/*
 * This software is distributed under following license based on modified BSD
 * style license.
 * ----------------------------------------------------------------------
 * 
 * Copyright 2009 The Nimbus2 Project. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer. 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE NIMBUS PROJECT ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
 * NO EVENT SHALL THE NIMBUS PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * The views and conclusions contained in the software and documentation are
 * those of the authors and should not be interpreted as representing official
 * policies, either expressed or implied, of the Nimbus2 Project.
 */
package jp.ossc.nimbus.service.message;

import java.io.*;
import java.util.*;
import java.net.*;

import jp.ossc.nimbus.core.*;
import jp.ossc.nimbus.io.*;

/**
 * メッセージレコード生成サービス。<p>
 * メッセージレコードのファイルからの読み込み及び外部提供を行う。<br>
 *
 * @author H.Nakano
 */
public class DefaultMessageRecordFactoryService extends ServiceBase
 implements MessageRecordFactory, DefaultMessageRecordFactoryServiceMBean{
    
    private static final long serialVersionUID = 2051325927530427511L;
    
    protected static final String C_DFAULT_DEF ="jp/ossc/nimbus/resource/Nimbus";
    protected static final String C_UNDER_SCORE = "_";
    protected static final String C_SLUSH = "/";
    protected static final String C_BKSLA = "\\";
    protected static final String C_PATH_DELIMETER = ";";
    protected static final String C_RECORD_DELIMETER = ",";
    protected static final String C_FOUND_DEF = "1";
    protected static final String C_NOT_FOUND_DEF = "0";
    
    /** ファイルディレクトリ */
    protected List<String> messageDirPaths = new ArrayList<String>();
    
    /** レコードキャッシュHASH */
    protected Map<String, MessageRecord> messageMap;
    
    /** ファイル拡張子 */
    protected String extention = "def";
    
    /** 秘密処理フラグ */
    protected boolean isSecret;
    
    /** 秘密文字 */
    protected String secretString;
    
    /** ロケール文字配列 */
    protected String[] initialLoadLocales;
    
    /** 検索済みロケールマップ */
    protected Set<Locale> serchedLocale;
    
    /** 検索済みパスマップ */
    protected Map<String, String> serchedPath;
    
    /** メッセージ定義ファイル名リスト */
    protected List<String> messageFileNames = new ArrayList<String>();
    
    protected boolean isAllowOverrideMessage;
    
    protected CSVReader csvReader;
    
    /**
     * コンストラクタ。<p>
     */
    public DefaultMessageRecordFactoryService(){
        messageFileNames.add(C_DFAULT_DEF);
    }
    
    // DefaultMessageRecordFactoryServiceMBeanのJavaDoc
    public void setMessageDirPaths(String[] paths){
        messageDirPaths.clear();
        if(paths != null){
            for(String path : paths){
                messageDirPaths.add(path);
            }
        }
    }
    
    // DefaultMessageRecordFactoryServiceMBeanのJavaDoc
    public String[] getMessageDirPaths(){
        return messageDirPaths.toArray(new String[messageDirPaths.size()]);
    }
    
    // DefaultMessageRecordFactoryServiceMBeanのJavaDoc
    public void addMessageDirPath(String path) throws Exception{
        if(!messageDirPaths.contains(path)){
            // ファイルリスト作成
            messageDirPaths.add(path);
            final File dir = new File(path);
            setMessageDef(dir);
            searchMessageDef(dir);
        }
    }
    
    // DefaultMessageRecordFactoryServiceMBeanのJavaDoc
    public void addMessageDirPaths(String[] paths) throws Exception{
        if(paths == null || paths.length == 0){
            return;
        }
        for(String path : paths){
            addMessageDirPath(path);
        }
    }
    
    // DefaultMessageRecordFactoryServiceMBeanのJavaDoc
    public void setExtentionOfMessageFile(String extention){
        this.extention = extention;
    }
    
    // DefaultMessageRecordFactoryServiceMBeanのJavaDoc
    public String getExtentionOfMessageFile(){
        return extention;
    }
    
    // DefaultMessageRecordFactoryServiceMBeanのJavaDoc
    public void setSecretMode(boolean flg){
        isSecret = flg;
    }
    
    // DefaultMessageRecordFactoryServiceMBeanのJavaDoc
    public boolean isSecretMode(){
        return isSecret;
    }
    
    // DefaultMessageRecordFactoryServiceMBeanのJavaDoc
    public void setSecretString(String secret){
        secretString = secret;
    }
    
    // DefaultMessageRecordFactoryServiceMBeanのJavaDoc
    public String getSecretString(){
        return secretString;
    }
    
    // DefaultMessageRecordFactoryServiceMBeanのJavaDoc
    public void setInitialLoadLocales(String[] locales){
        initialLoadLocales = locales;
    }
    
    // DefaultMessageRecordFactoryServiceMBeanのJavaDoc
    public String[] getInitialLoadLocales(){
        return initialLoadLocales;
    }
    
    // DefaultMessageRecordFactoryServiceMBeanのJavaDoc
    public void setMessageFiles(String[] files){
        messageFileNames.clear();
        if(files != null){
            for(String file : files){
                messageFileNames.add(file);
            }
        }
        messageFileNames.add(C_DFAULT_DEF);
    }
    
    // DefaultMessageRecordFactoryServiceMBeanのJavaDoc
    public String[] getMessageFiles(){
        return (String[])messageFileNames.toArray(
            new String[messageFileNames.size()]
        );
    }
    
    // DefaultMessageRecordFactoryServiceMBeanのJavaDoc
    public void addMessageFile(String file) throws Exception {
        if(!messageFileNames.contains(file)){
            URL url = Thread.currentThread().getContextClassLoader()
                .getResource(file + '.' + extention);
            if(url != null){
                // テーブル作成
                readStream(url.openStream(), null);
                if(initialLoadLocales != null){
                    for(String locale : initialLoadLocales){
                        loadDefFileFromResource(locale, file);
                    }
                }
            }
        }
    }
    
    // DefaultMessageRecordFactoryServiceMBeanのJavaDoc
    public void addMessageFiles(String[] files) throws Exception {
        if(files == null || files.length == 0){
            return;
        }
        for(String file : files){
            addMessageFile(file);
        }
    }
    
    // DefaultMessageRecordFactoryServiceMBeanのJavaDoc
    public void setAllowOverrideMessage(boolean isAllow){
        isAllowOverrideMessage = isAllow;
    }
    
    // DefaultMessageRecordFactoryServiceMBeanのJavaDoc
    public boolean isAllowOverrideMessage(){
        return isAllowOverrideMessage;
    }
    
    public void setCSVReader(CSVReader reader){
        csvReader = reader;
    }
    public CSVReader getCSVReader(){
        return csvReader;
    }
    
    /**
     * サービスの生成処理を行う。<p>
     *
     * @exception Exception 生成処理に失敗した場合
     */
    public void createService() throws Exception{
        messageMap = new HashMap<String, MessageRecord>();
        serchedLocale = new HashSet<Locale>();
        serchedPath = new HashMap<String, String>();
    }
    
    /**
     * サービスの開始処理を行う。<p>
     * メッセージ定義ファイルのロードを行う。<br>
     *
     * @exception Exception 開始処理に失敗した場合
     */
    public void startService() throws Exception{
        if(csvReader == null){
            csvReader = new CSVReader();
            csvReader.setCommentPrefix("#");
            csvReader.setIgnoreEmptyLine(true);
        }
        
        // ディレクトリ指定のメッセージ定義ファイルのロード
        for(String path : messageDirPaths){
            final File dir = new File(path);
            
            // デフォルトロケールのメッセージ定義ファイルをロードする
            setMessageDef(dir);
            
            // 設定されたロケールのメッセージ定義ファイルをロードする
            searchMessageDef(dir);
        }
        
        // ファイル指定のメッセージ定義ファイルのロード
        for(String fileName : messageFileNames){
            final URL url = Thread.currentThread().getContextClassLoader()
                .getResource(fileName + '.' + extention);
            
            if(url != null){
                // デフォルトロケールのメッセージ定義ファイルをロードする
                readStream(url.openStream(), null);
                
                // 設定されたロケールのメッセージ定義ファイルをロードする
                if(initialLoadLocales != null){
                    for(String locale : initialLoadLocales){
                        loadDefFileFromResource(locale, fileName);
                    }
                }
            }
        }
    }
    
    /**
     * サービスの停止処理を行う。<p>
     *
     * @exception Exception 停止処理に失敗した場合
     */
    public void stopService() throws Exception{
        messageMap.clear();
        serchedLocale.clear();
        serchedPath.clear();
    }
    
    /**
     * サービスの破棄処理を行う。<p>
     *
     * @exception Exception 破棄処理に失敗した場合
     */
    public void destroyService() throws Exception{
        messageMap = null;
        serchedLocale = null;
        serchedPath = null;
    }
    
    /**
     * ディレクトリ指定時のResourceBundle読み込みを行う。<p>
     * 
     * @param lo ロケール
     * @param dirPath ディレクトリパス
     * @exception IOException メッセージ定義ファイルの読み込みに失敗した場合
     * @exception MessageRecordParseException メッセージ定義ファイルのパースに失敗した場合
     */
    protected void loadDirByLocale(Locale lo, String dirPath)
     throws IOException, MessageRecordParseException{
        dirPath = dirPath.replaceAll(C_BKSLA, C_SLUSH);
        
        //language1 + "_" + country1 + "_" + variant1
        StringBuilder propKey = new StringBuilder(dirPath);
        propKey.append(C_SLUSH).append(lo.getLanguage())
            .append(C_UNDER_SCORE).append(lo.getCountry())
            .append(C_UNDER_SCORE).append(lo.getVariant());
        String key = propKey.toString();
        File dirFile = null;
        if(!serchedPath.containsKey(key)){
            dirFile = new File(key);
            if(addMessageDef(dirFile)){
                return;
            }
        }
        
        //language1 + "_" + country1
        propKey.setLength(0);
        propKey.append(dirPath)
            .append(C_SLUSH).append(lo.getLanguage())
            .append(C_UNDER_SCORE).append(lo.getCountry()); 
        key = propKey.toString();
        if(!serchedPath.containsKey(key)){
            dirFile = new File(key);
            if(addMessageDef(dirFile)){
                return;
            }
        }
        
        // language1
        propKey.setLength(0);
        propKey.append(dirPath)
            .append(C_SLUSH).append(lo.getLanguage());
        key = propKey.toString();
        if(!serchedPath.containsKey(key)){
            dirFile = new File(key);
            if(addMessageDef(dirFile)){
                return;
            }
        }
        
        if(lo.equals(Locale.getDefault())){
            return;
        }
        
        // language1 + "_" + country1 + "_" + variant1
        Locale loDafault = Locale.getDefault();
        propKey.setLength(0);
        propKey.append(dirPath)
            .append(C_SLUSH).append(loDafault.getLanguage())
            .append(C_UNDER_SCORE).append(loDafault.getCountry())
            .append(C_UNDER_SCORE).append(loDafault.getVariant());
        key = propKey.toString();
        if(!serchedPath.containsKey(key)){
            dirFile = new File(key);
            if(addMessageDef(dirFile)){
                return;
            }
        }
        
        //language1 + "_" + country1
        propKey.setLength(0);
        propKey.append(dirPath)
            .append(C_SLUSH).append(loDafault.getLanguage())
            .append(C_UNDER_SCORE).append(loDafault.getCountry()); 
        key = propKey.toString();
        if(!serchedPath.containsKey(key)){
            dirFile = new File(key);
            if(addMessageDef(dirFile)){
                return;
            }
        }
        
        // language1  
        propKey.setLength(0);
        propKey.append(dirPath)
            .append(C_SLUSH).append(loDafault.getLanguage()); 
        key = propKey.toString();
        if(!serchedPath.containsKey(key)){
            dirFile = new File(key);
            if(addMessageDef(dirFile)){
                return;
            }
        }
    }
    
    /**
     * クラスパス内から指定DEFファイルのResourceBundle読み込みを行う。<p>
     * 
     * @param lo ロケール
     * @param defFileName 指定ファイル名
     * @exception IOException メッセージ定義ファイルの読み込みに失敗した場合
     * @exception MessageRecordParseException メッセージ定義ファイルのパースに失敗した場合
     */
    protected void loadClassPathByLocale(Locale lo, String defFileName)
     throws IOException, MessageRecordParseException{
        StringBuilder propKey = new StringBuilder();
        
        //language1 + "_" + country1 + "_" + variant1
        propKey.append(lo.getLanguage())
            .append(C_UNDER_SCORE).append(lo.getCountry())
            .append(C_UNDER_SCORE).append(lo.getVariant());
        String fileName = defFileName + '_' + propKey.toString()
             +  '.' + extention;
        if(!serchedPath.containsKey(fileName)){
            if(loadDefFileFromResource(propKey.toString(), defFileName)){
                return;
            }
        }
        
        //language1 + "_" + country1
        propKey.setLength(0);
        propKey.append(lo.getLanguage())
            .append(C_UNDER_SCORE).append(lo.getCountry());
        fileName = defFileName + '_' + propKey.toString() +  '.' + extention;
        if(!serchedPath.containsKey(fileName)){
            if(loadDefFileFromResource(propKey.toString(), defFileName)){
                return;
            }
        }
        
        // language1
        propKey.setLength(0);
        propKey.append(lo.getLanguage());
        fileName = defFileName + '_' + propKey.toString() +  '.' + extention;
        if(!serchedPath.containsKey(fileName)){
            if(loadDefFileFromResource(propKey.toString(), defFileName)){
                return;
            }
        }
        
        if(lo.equals(Locale.getDefault())){
            return;
        }
        
        // language1 + "_" + country1 + "_" + variant1
        Locale loDafault = Locale.getDefault();
        propKey.setLength(0);
        propKey.append(loDafault.getLanguage())
            .append(C_UNDER_SCORE).append(loDafault.getCountry())
            .append(C_UNDER_SCORE).append(loDafault.getVariant()); 
        fileName = defFileName + '_' + propKey.toString() +  '.' + extention;
        if(!serchedPath.containsKey(fileName)){
            if(loadDefFileFromResource(propKey.toString(), defFileName)){
                return;
            }
        }
        
        //language1 + "_" + country1
        propKey.setLength(0);
        propKey.append(loDafault.getLanguage())
            .append(C_UNDER_SCORE).append(loDafault.getCountry());
        fileName = defFileName + '_' + propKey.toString() +  '.' + extention;
        if(!serchedPath.containsKey(fileName)){
            if(loadDefFileFromResource(propKey.toString(), defFileName)){
                return;
            }
        }
        
        // language1
        propKey.setLength(0);
        propKey.append(loDafault.getLanguage()); 
        fileName = defFileName + '_' + propKey.toString() +  '.' + extention;
        if(!serchedPath.containsKey(fileName)){
            if(loadDefFileFromResource(propKey.toString(), defFileName)){
                return;
            }
        }
    }
    
    /**
     * 指定されたロケールのメッセージ定義ファイルをリソースとして読み込んで格納する。<p>
     *
     * @param loString ロケール文字列
     * @param defName メッセージ定義ファイル名
     * @return 見つかったらtrue
     * @exception IOException メッセージ定義ファイルの読み込みに失敗した場合
     * @exception MessageRecordParseException メッセージ定義ファイルのパースに失敗した場合
     */
    protected boolean loadDefFileFromResource(String loString, String defName)
     throws IOException, MessageRecordParseException{
        
        String fileName = defName + '_' + loString +  '.' + extention;
        boolean result = false;
        final ClassLoader classLoader
             = Thread.currentThread().getContextClassLoader();
        final URL url = classLoader.getResource(fileName);
        try{
            if(url != null){
                readStream(url.openStream(), loString);
                serchedPath.put(fileName, C_FOUND_DEF);
                result = true;
            }else{
                serchedPath.put(fileName,C_NOT_FOUND_DEF);
            }
        }catch(IOException e){
            serchedPath.put(fileName, C_NOT_FOUND_DEF) ;
        }
        return result;
    }
    
    /**
     * 指定されたディレクトリ配下から、設定されたロケールのロケール名ディレクトリを検索して、そのディレクトリ配下のメッセージ定義ファイルを読み込んで格納する。<p>
     * 
     * @param dirRoot メッセージ定義ファイルを格納したディレクトリ
     * @exception IOException メッセージ定義ファイルの読み込みに失敗した場合
     * @exception MessageRecordParseException メッセージ定義ファイルのパースに失敗した場合
     */
    protected void searchMessageDef(File dirRoot)
     throws IOException, MessageRecordParseException{
        if(initialLoadLocales == null || initialLoadLocales.length == 0){
            return;
        }
        File[] dirs = dirRoot.listFiles();
        if(dirs != null){
            for(File dir : dirs){
                if(dir.isDirectory()){
                    name = dir.getName();
                    for(String locale : initialLoadLocales){
                        if(name.equals(locale)){
                            addMessageDef(dir);
                            break;
                        }
                    }
                }
            }
        }
    }
    
    /**
     * 指定されたディレクトリ配下のメッセージ定義ファイルを読み込んで、このサービスに格納する。<p>
     * 
     * @param dirRoot メッセージ定義ファイルが格納されたディレクトリ
     * @exception IOException メッセージ定義ファイルの読み込みに失敗した場合
     * @exception MessageRecordParseException メッセージ定義ファイルのパースに失敗した場合
     */
    protected void setMessageDef(File dirRoot)
     throws IOException, MessageRecordParseException{
        ExtentionFileFilter filter = new ExtentionFileFilter(extention);
        File[] defFileList = dirRoot.listFiles(filter);
        if(defFileList != null){
            for(File file : defFileList){
                if(file.isFile()){
                    // ファイルOPEN
                    FileInputStream stream = null;
                    try{
                        stream = new FileInputStream(file);
                    }catch(FileNotFoundException e){
                        continue;
                    }
                    readStream(stream, null);
                }
            }
        }
    }
    
    /**
     * 指定されたディレクトリ配下のメッセージ定義ファイルを読み込んで、このサービスに格納する。<p>
     * 
     * @param dir メッセージ定義ファイルが格納されたディレクトリ
     * @return メッセージ定義ファイルを読み込んだ場合true
     * @exception IOException メッセージ定義ファイルの読み込みに失敗した場合
     * @exception MessageRecordParseException メッセージ定義ファイルのパースに失敗した場合
     */
    protected boolean addMessageDef(File dir)
     throws IOException, MessageRecordParseException{
        ExtentionFileFilter filter = new ExtentionFileFilter(extention);
        File[] defFileList = dir.listFiles(filter);
        boolean bret = false;
        if(defFileList != null){
            for(File file : defFileList){
                if(file.isDirectory()){
                    continue;
                }
                // ファイルOPEN
                FileInputStream stream = null ;
                try{
                    stream = new FileInputStream(file);
                }catch(FileNotFoundException e){
                    continue;
                }
                readStream(stream,dir.getName());
            }
            serchedPath.put(dir.getAbsolutePath(), C_FOUND_DEF) ;
            bret = true;
        }else{
            serchedPath.put(dir.getAbsolutePath(), C_NOT_FOUND_DEF) ;
        }
        return bret;
    }
    
    /**
     * 指定された入力ストリームのメッセージ定義ファイルを、指定されたロケールのメッセージ定義として読み込んで、格納する。<p>
     *
     * @param stream メッセージ定義ファイルの入力ストリーム
     * @param locale ロケール文字列
     * @exception IOException メッセージ定義ファイルの読み込みに失敗した場合
     * @exception MessageRecordParseException メッセージ定義ファイルのパースに失敗した場合
     */
    private void readStream(InputStream stream, String locale)
     throws IOException, MessageRecordParseException{
        final UnicodeHexBufferedReader in = new UnicodeHexBufferedReader(
            new InputStreamReader(stream)
        );
        final CSVReader reader = csvReader.cloneReader();
        reader.setReader(in);
        
        // テーブル作成
        List<String> csv = null;
        try{
            while((csv = reader.readCSVLineList(csv)) != null){
                if(locale == null){
                    putDefRec(csv);
                }else{
                    addDefRec(csv, locale);
                }
            }
        }finally{
            in.close();
            stream.close();
        }
    }
    
    /**
     * 指定したロケールのメッセージ定義レコードを追加する。<p>
     * 
     * @param record メッセージ定義レコード
     * @param locale ロケール
     * @exception MessageRecordParseException メッセージ定義ファイルのパースに失敗した場合
     */
    protected void addDefRec(List<String> record, String locale)
     throws MessageRecordParseException{
        //Messae Record作成
        if(record.size() < 2){
            throw new MessageRecordParseException(
                "Record format error. record=" + record
            );
        }
        MessageRecord messageRec = messageMap.get(record.get(0));
        if(messageRec == null){
            throw new MessageRecordParseException(
                "No such record. id=" + record.get(0)
            );
        }
        messageRec.addMessage(record, locale);
    }
    
    /**
     * メッセージ定義レコードを登録する。<p>
     *
     * @param record メッセージ定義レコード
     * @exception MessageRecordParseException メッセージ定義ファイルのパースに失敗した場合
     */
    protected void putDefRec(List<String> record)
     throws MessageRecordParseException{
        //Messae Record作成
        MessageRecord rec = createMessageRecord(record);
        //管理HASH投入
        final MessageRecord tmpRec
             = messageMap.get(rec.getMessageCode());
        if(tmpRec == null){
            messageMap.put(rec.getMessageCode(), rec);
        }else if(!isAllowOverrideMessage){
            throw new MessageRecordParseException(
                "Message code duplicate. recode=" + record
            );
        }
    }
    
    protected MessageRecord createMessageRecord(List<String> record)
     throws MessageRecordParseException{
        MessageRecord rec = createMessageRecord();
        rec.rec2Obj(record);
        return rec;
    }
    
    protected MessageRecord createMessageRecord(){
        MessageRecord rec = new MessageRecordImpl();
        rec.setMessageRecordFactory(this);
        rec.setSecret(isSecret);
        rec.setSecretString(secretString);
        return rec;
    }
    
    // DefaultMessageRecordFactoryServiceMBeanのJavaDoc
    public List<MessageRecord> getMessageList(){
        List<MessageRecord> retAry = new ArrayList<MessageRecord>();
        synchronized(messageMap){
            retAry.addAll(messageMap.values());
        }
        return retAry;
    }
    
    // MessageRecordFactoryのJavaDoc
    public MessageRecord findMessageRecord(String messageId){
        MessageRecord rec = null;
        synchronized(messageMap){
            rec = messageMap.get(messageId);
            if(rec != null){
                rec.setSecret(isSecret);
                rec.setSecretString(secretString);
            }
        }
        return rec;
    }
    
    // MessageRecordFactoryのJavaDoc
    public String findMessageTemplete(String messageId){
        MessageRecord rec = findMessageRecord(messageId);
        return rec == null ? null : rec.getMessageTemplate();
    }
    
    // MessageRecordFactoryのJavaDoc
    public String findMessageTemplete(String messageId, Locale lo){
        MessageRecord rec = findMessageRecord(messageId);
        return rec == null ? null : rec.getMessageTemplate(lo);
    }
    
    // MessageRecordFactoryのJavaDoc
    public String findMessage(String messageId, Object... embeds){
        MessageRecord rec = findMessageRecord(messageId);
        return rec == null ? null : rec.makeMessage(embeds);
    }
    
    // MessageRecordFactoryのJavaDoc
    public String findMessage(String messageId, Locale lo, Object... embeds){
        MessageRecord rec = this.findMessageRecord(messageId);
        return rec == null ? null : rec.makeMessage(lo, embeds);
    }
    
    // MessageRecordFactoryのJavaDoc
    public void findLocale(Locale lo){
        synchronized(serchedLocale){
            Locale tmpLo = null;
            if(lo == null){
                tmpLo = Locale.getDefault();
            }else{
                tmpLo = lo;
            }
            if(!serchedLocale.contains(tmpLo)){
                boolean isLoad = false;
                for(String path : messageDirPaths){
                    try{
                        loadDirByLocale(tmpLo, path);
                        isLoad = true;
                    }catch(Exception e){
                    }
                }
                for(String fileName : messageFileNames){
                    try{
                        loadClassPathByLocale(tmpLo, fileName);
                        isLoad = true;
                    }catch(Exception e){
                    }
                }
                if(isLoad){
                    serchedLocale.add(tmpLo);
                }
            }
        }
    }
    
    public static class MessageRecordImpl
     implements MessageRecord, Serializable{
        
        private static final long serialVersionUID = -1519744660770872465L;
        
        /** デフォルトカテゴリー */
        protected static final String C_DFAUTL_LOCALE = "default";
        protected static final String C_UNDER_SCORE = "_";
        
        /** 埋め込み文字 */
        protected static final String EMBED_STRING = "@";
        protected static final String SECRET_EMBED_STRING = "#";
        protected static final String SINGLE_EMBED_STRING = "@0";
        protected static final String SINGLE_SECRET_EMBED_STRING = "#0";
        protected static final String SECRET_EMBED_SEARCH_STRING = "#[0-9]+";
        
        /** メッセージID */
        protected String code;
        
        /** ロケールメッセージマップ */
        protected Map<String, String> messageMap = new HashMap<String, String>();
        
        /** 秘密文字フラグ */
        protected boolean isSecret = false;
        
        /** 秘密文字 */
        protected String secretString;
        
        /** ロード済みロケｰルマップ */
        protected Map<Locale, String> localeMap = new HashMap<Locale, String>();
        
        /** MessageRecordFactory */
        protected transient MessageRecordFactory messageRecordFactory;
        
        /** MessageRecordFactoryサービス名 */
        protected ServiceName messageRecordFactoryName;
        
        // MessageRecordのJavaDoc
        public String getMessageTemplate(Locale lo){
            return getMessage(lo == null ? Locale.getDefault() : lo);
        }
        
        // MessageRecordのJavaDoc
        public String getMessageTemplate(){
            return getMessageTemplate(Locale.getDefault());
        }
        
        // MessageRecordのJavaDoc
        public String makeMessage(Locale lo, Object... embeds){
            String retStr = getMessageTemplate(lo);
            if(embeds != null && embeds.length != 0){
                retStr = replaceAll(retStr, EMBED_STRING, embeds);
            }
            if(isSecret){
                if(secretString != null){
                    retStr = retStr.replaceAll(
                        SECRET_EMBED_SEARCH_STRING,
                        secretString
                    );
                }
            }else{
                retStr = replaceAll(
                    retStr,
                    SECRET_EMBED_STRING,
                    embeds
                );
            }
            return retStr;
        }
        
        private String replaceAll(String in, String target, Object[] replaces){
            if(replaces == null || replaces.length == 0){
                return in;
            }
            for(int i = 0; i < replaces.length ; i++){
                in = in.replaceAll(
                    target + i,
                    replaces[i] == null ? "null" : java.util.regex.Matcher.quoteReplacement(replaces[i].toString())
                );
            }
            return in;
        }
        
        // MessageRecordのJavaDoc
        public String makeMessage(Object... embeds){
            return makeMessage(Locale.getDefault(), embeds);
        }
        
        // MessageRecordのJavaDoc
        public void rec2Obj(List<String> record) throws MessageRecordParseException{
            // デファイン文字分割
            if(record.size() < 2){
                throw new MessageRecordParseException(
                    "Message define error. record=" + record
                ) ;
            }else{
                // 基本データ格納
                code = record.get(0);
                messageMap.put(C_DFAUTL_LOCALE, record.get(1));
            }
        }
        
        // MessageRecord のJavaDoc
        public String getMessageCode(){
            return this.code;
        }
        
        /**
         * メッセージIDを設定する。<p>
         *
         * @param code メッセージID
         */
        public void setMessageCode(String code){
            this.code = code;
        }
        
        // MessageRecord のJavaDoc
        public void setSecret(boolean flg){
            isSecret = flg;
        }
        
        // MessageRecord のJavaDoc
        public void setSecretString(String secret){
            secretString = secret;
        }
        
        // MessageRecord のJavaDoc
        public void addMessage(List<String> record, String locale){
            messageMap.put(locale, record.get(1));
        }
        
        /**
         * 指定したロケールのメッセージを取得する。<p>
         * 
         * @param lo ロケール
         * @return 指定したロケールのメッセージ
         */
        protected String getMessage(Locale lo){
            MessageRecordFactory fac = null;
            if(messageRecordFactory == null && messageRecordFactoryName != null){
                try{
                    fac = (MessageRecordFactory)ServiceManagerFactory
                        .getServiceObject(messageRecordFactoryName);
                }catch(ServiceNotFoundException e){
                    return null;
                }
            }else{
                fac = messageRecordFactory;
            }
            if(fac == null){
                return null;
            }
            fac.findLocale(lo);
            String key = (String)localeMap.get(lo);
            if(key == null){
                StringBuilder propKey = new StringBuilder();
                //language1 + "_" + country1 + "_" + variant1 
                propKey.append(lo.getLanguage())
                    .append(C_UNDER_SCORE).append(lo.getCountry())
                    .append(C_UNDER_SCORE).append(lo.getVariant());
                key = (String)messageMap.get(propKey.toString());
                
                if(key == null){
                    //language1 + "_" + country1
                    propKey.setLength(0);
                    propKey.append(lo.getLanguage())
                        .append(C_UNDER_SCORE).append(lo.getCountry());
                    key = (String)messageMap.get(propKey.toString());
                }
                
                if(key == null){
                    // language1  
                    propKey.setLength(0);
                    propKey.append(lo.getLanguage());
                    key = (String)messageMap.get(propKey.toString());
                }
                
                if(key == null){
                    if(!lo.equals(Locale.getDefault())){
                        
                        Locale loDafault = Locale.getDefault();
                        
                        // language1 + "_" + country1 + "_" + variant1 
                        propKey.setLength(0);
                        propKey.append(loDafault.getLanguage())
                            .append(C_UNDER_SCORE).append(loDafault.getCountry())
                            .append(C_UNDER_SCORE).append(loDafault.getVariant());
                        key = (String)messageMap.get(propKey.toString());
                        if(key == null){
                            //language1 + "_" + country1
                            propKey.setLength(0);
                            propKey.append(loDafault.getLanguage())
                                .append(C_UNDER_SCORE).append(loDafault.getCountry()); 
                            key = (String)messageMap.get(propKey.toString());
                        }
                        if(key == null){
                            // language1  
                            propKey.setLength(0);
                            propKey.append(loDafault.getLanguage()); 
                            key = (String)messageMap.get(propKey.toString());
                        }
                    }
                    key = (String)messageMap.get(C_DFAUTL_LOCALE);
                }
                
                localeMap.put(lo, key);
            }
            return key;
        }
        
        // MessageRecordOperator のJavaDoc
        public void setMessageRecordFactory(MessageRecordFactory fac){
            messageRecordFactory = fac;
            if(messageRecordFactory instanceof ServiceBase){
                messageRecordFactoryName = ((ServiceBase)messageRecordFactory).getServiceNameObject();
            }else if(messageRecordFactory instanceof Service){
                final Service service = (Service)messageRecordFactory;
                messageRecordFactoryName = new ServiceName(
                    service.getServiceManagerName(),
                    service.getServiceName()
                );
            }
        }
        
        /**
         * 文字列表現を取得する。<p>
         * 
         * @return 文字列表現
         */
        public String toString(){
            StringBuilder buf = new StringBuilder(super.toString());
            buf.append('{');
            buf.append("code=").append(code).append(',');
            buf.append("message=").append(messageMap);
            buf.append('}');
            return buf.toString();
        }
        
        private void writeObject(ObjectOutputStream out) throws IOException{
            out.defaultWriteObject();
            if(messageRecordFactoryName == null
                    && messageRecordFactory != null){
                out.writeObject(messageRecordFactory);
            }
        }
        
        private void readObject(ObjectInputStream in)
         throws IOException, ClassNotFoundException{
            in.defaultReadObject();
            if(messageRecordFactoryName == null){
                messageRecordFactory = (MessageRecordFactory)in.readObject();
            }else{
                try{
                    messageRecordFactory
                        = (MessageRecordFactory)ServiceManagerFactory
                            .getServiceObject(messageRecordFactoryName);
                }catch(ServiceNotFoundException e){
                }
            }
        }
    }
}
