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

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;

import jp.hrtdotnet.java.io.HDirectory;
import jp.hrtdotnet.java.io.HFile;

/**
 * Propertiesクラスを拡張したプロパティ情報を扱うクラスです。
 * このクラスではプロパティ情報をLinkedHashMapを利用することで登録した順番を保持します。
 * <br><br>
 * 拡張された機能は以下の通りです。
 * <ul>
 * <li>LinkedHashMapで管理</li>
 * <li>プロパティ情報のファイル出力で追記書込み</li>
 * </ul>
 * 
 * @version 2.0
 * @author hrtdotnet.jp
 * <br>http://www.hrtdotnet.jp/
 * @since 2.0
 */
public class HProperties extends Properties {

    private static final long serialVersionUID = 8613893331398624211L;
    protected Map map = Collections.synchronizedMap(new LinkedHashMap());

    /**
     * コンストラクタ。
     * @since 2.0
     */
    public HProperties() {
        this(null);
    }

    /**
     * コンストラクタ。
     * @param prop 既存のプロパティオブジェクト
     * @since 2.0
     */
    public HProperties(HProperties prop) {
        super();
        if (prop != null) {
            Enumeration e = prop.propertyNames();
            while (e.hasMoreElements()) {
                String key = (String)e.nextElement();
                this.setProperty(key, prop.getProperty(key));
            }
        }
    }

    /**
     * プロパティ項目のキーと値を登録します。
     * @param key キー
     * @param value オブジェクト
     * @return 登録されたオブジェクト
     * @since 2.0
     */
    public synchronized Object setProperty(String key, String value) {
        return put(key, value);
    }

    /**
     * @deprecated 外部から使ってはなりません。
     * {@link #setProperty(String, String) setProperty(String, String)} を利用してください。
     * @since 2.0
     */
    public synchronized Object put(Object key, Object value){
        return map.put(key, value);
    }
    

    /**
     * @deprecated 外部から使ってはなりません。
     * {@link #propertyNames() propertyNames()} を利用してください。
     * @since 2.0
     */
    public synchronized Enumeration keys(){
        return Collections.enumeration(map.keySet());
    }

    /**
     * <p>
     * プロパティ項目として登録されているキーの列挙体を取得します。
     * </p>
     * @return キーの列挙体
     * @since 2.0
     */
    public synchronized Enumeration propertyNames(){
        return keys();
    }

    /**
     * キーにマップされているプロパティ値を取得します。
     * 
     * @param key キー
     * @return キーにマップされているプロパティ値
     * @since 2.0
     */
    public String getProperty(String key) {
        return (String)get(key);
    }

    /**
     * キーにマップされているプロパティ値を取得します。
     * 指定したキーに対応するプロパティ値が登録されていない場合は指定したデフォルト文字列を返します。

     * @param key キー
     * @param defaultValue デフォルト文字列
     * @return キーにマップされているプロパティ値
     * @since 2.0
     */
    public String getProperty(String key, String defaultValue) {
        String value = getProperty(key);
        if(value == null) return defaultValue;
        return value;
    }
    
    /**
     * @deprecated 外部から使ってはなりません。
     * {@link #getProperty(String) getProperty(String)} を利用してください。
     *          
     * @since 2.0
     */
    public Object get(Object key){
        return map.get(key);
    }

    
    /**
     * プロパティ情報をファイルに出力します。
     * 指定したプロパティファイルがすでに存在していて、
     * プロパティ情報が設定されている場合はその情報はマージされます。
     * ただしプロパティファイルのキーと同一のキーのプロパティ情報がオブジェクトに
     * 登録されている場合はオブジェクトのプロパティ情報で上書きされます。
     * 
     * @param filePath プロパティファイルパス
     * @param header ヘッダ情報
     * @throws NullPointerException
     *          filePathがnullの場合
     * @throws FileNotFoundException
     *          ファイルが存在していなかった場合
     * @throws IOException
     *          プロパティ情報をファイルに書き込めなかった場合
     * @throws ClassCastException
     *          このPropertiesオブジェクトに、Stringではないキーまたは値が格納されている場合 
     *          
     * @since 2.0
     */
    public void store(String filePath, String header) throws IOException {
        store(filePath, header, true);
    }
    
    /**
     * プロパティ情報をファイルに出力します。
     * 指定したプロパティファイルがすでに存在している場合に
     * ファイルの上書きをするかプロパティ情報のマージをするかを指定します。
     * ただしマージの場合、プロパティファイルのキーと同一のキーのプロパティ情報が
     * オブジェクトに登録されている場合はオブジェクトのプロパティ情報で上書きされます。
     * 
     * @param filePath プロパティファイルパス
     * @param header ヘッダ情報
     * @param b マージはtrue、上書きはfalse
     * @throws NullPointerException
     *          filePathがnullの場合
     * @throws FileNotFoundException
     *          ファイルが存在していなかった場合
     * @throws IOException
     *          プロパティ情報をファイルに書き込めなかった場合
     * @throws ClassCastException
     *          このPropertiesオブジェクトに、Stringではないキーまたは値が格納されている場合 
     * @since 2.0
     */
    public void store(String filePath, String header, boolean b) throws IOException {

        if (filePath == null) {
            throw new NullPointerException("filePath == null");
        }
        
        HProperties prop = new HProperties();
        if (b) {
            InputStream is = null;
            try {
                is = new FileInputStream(filePath);
                prop.load(is);
            } finally {
                if (is != null) {
                    is.close();
                }
            }
        }
        prop.map.putAll(map);

        OutputStream os = null;
        try {
            HFile f = null;
            f = new HFile(filePath);
            HDirectory d = new HDirectory(f.getParent());
            if(!d.exists()) {
                d.mkdirs();
            }
            os = new FileOutputStream(f);
            prop.store(os, header);
        } finally {
            if (os != null) {
                os.close();
            }
        }
    }
    
    /**
     * Propertiesクラスのstoreメソッドと同じ処理をします。
     * マージはされません。
     * 
     * @param out プロパティファイルパス
     * @param header ヘッダ情報
     * @throws IOException
     *          このプロパティリストを指定した出力ストリームに書き込んで、IOException がスローされた場合 
     * @throws ClassCastException
     *          この Properties オブジェクトに、String ではないキーまたは値が格納されている場合 
     * @throws NullPointerException 
     *          outがnullの場合
     * @since 2.0
     */
    public void store(OutputStream out, String header) throws IOException {        
        super.store(out, header);
    }
    

    /**
     * 指定された出力ストリームに、プロパティリストを出力します。
     * このメソッドはデバッグに利用します。
     * 
     * @param out 出力ストリーム
     * @since 2.0
     */
    public void list(PrintStream out) {
        out.println("-- listing properties --");
        LinkedHashMap map = new LinkedHashMap();
        enumerate(map);
        for (Enumeration e = Collections.enumeration(map.keySet()); e.hasMoreElements() ;) {
            String key = (String)e.nextElement();
            String value = (String)map.get(key);
            out.println(key + "=" + value);
        }
        out.flush();
    }

    /**
     * 指定された出力ストリームに、プロパティリストを出力します。
     * このメソッドはデバッグに利用します。
     * 
     * @param out 出力ストリーム
     * @since 2.0
     */
    public void list(PrintWriter out) {
        out.println("-- listing properties --");
        LinkedHashMap map = new LinkedHashMap();
        enumerate(map);
        for (Enumeration e = Collections.enumeration(map.keySet()); e.hasMoreElements() ;) {
            String key = (String)e.nextElement();
            String value = (String)map.get(key);
            out.println(key + "=" + value);
        }
        out.flush();
    }
    
    private synchronized void enumerate(Map m) {
        for (Enumeration e = keys() ; e.hasMoreElements() ;) {
            String key = (String)e.nextElement();
            m.put(key, get(key));
        }
    }
}
