
/*
このファイルの中身は、NTT オープンソースライセンス　バージョン 1.0（「本
契約」という）の適用を受けます。
本契約を遵守しない限り、このファイルを使用してはなりません。
本契約のコピーは、次のＵＲＬから入手できます。
【配布サイトのURL】 http://www.oss.ecl.ntt.co.jp/lms/

本契約に基づき頒布されるソフトウェアは、現状のまま、明示又は黙示のあらゆ
る種類の保証なしで、頒布されます。本契約に基づく権利及び制限を律する特定
の文言については、本契約を参照してください。

「オリジナルコード」は、 NTT Cyber Space Laboratories Code です。 
「オリジナルコード」の「初期開発者」は、日本電信電話株式会社です。  
日本電信電話株式会社により創作された部分は、Copyright (C) 2004 
日本電信電話株式会社 です。
全ての権利が留保されます。 
「コントリビュータ」：_____________________________________ 


The contents of this file are subject to the NTT Opensource License
Version 1.0 (the License); you may not use this file except in
compliance with the License. You may obtain a copy of the License at
【配布サイトのURL】 http://www.oss.ecl.ntt.co.jp/lms/

Software distributed under the License is distributed on an AS IS
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
License for the specific language governing rights and limitations
under the License.

The Original Code is NTT Cyber Space Laboratories Code .

The Initial Developer of the Original Code is NIPPON TELEGRAPH AND 
TELEPHONE CORPORATION.
Portions created by the NIPPON TELEGRAPH AND TELEPHONE CORPORATION 
are Copyright (C) 2004 NIPPON TELEGRAPH AND TELEPHONE CORPORATION. 
All Rights Reserved.

Contributor(s) ______________________________________.
*/
package jp.co.ntt.lms.installer.core.util;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Iterator;
import java.util.Properties;
import java.util.logging.Logger;

import jp.co.ntt.lms.installer.core.InstallerException;
import jp.co.ntt.lms.installer.core.InstallerOverwritePropertiesValue;
import jp.co.ntt.lms.installer.core.InstallerValues;
import jp.co.ntt.lms.installer.util.LoggerUtil;
import jp.co.ntt.lms.installer.util.StringUtil;

/**
 * プロパティに関するユーティリティ・クラス。
 * @author T.Nishiki
 */
public class InstallerPropertiesUtil {
	/**
	 * ログ出力オブジェクト。
	 */
	private static Logger _logger;

	/**
	 * スタティック・イニシャライザ。<br>
	 * ログ出力オブジェクトを取得します。
	 */
	static {
		_logger = LoggerUtil.getLogger(InstallerPropertiesUtil.class);
	}

	/**
	 * 指定されたキーに対応するプロパティの初期値を取得します。<br>
	 * 基点となるプロパティと、参照するプロパティとを見比べて、
	 * 両者の指定のプロパティに差異がある場合は、参照するプロパティ値をします。
	 * 差異がない場合は、nullを戻します。
	 *
	 * @param targetProperties 差異を取得するプロパティ。
	 * @param originProperties 差異の基点となるプロパティ。
	 * @param key 差異を確認するプロパティ・キー。
	 * @return 指定されたキーでプロパティに差異があれば、
	 *          targetPropertiesでの値を戻す。
	 *          差異がなければnullを戻す。
	 */
	public static String determineInitialValue(
			Properties targetProperties,
			Properties originProperties, String key) {
		String difference = null;
		String targetProperty = targetProperties.getProperty(key);
		String originProperty = originProperties.getProperty(key);
		if (StringUtil.isEmpty(targetProperty)) {
			difference = null;
		}
		else if (StringUtil.isEmpty(originProperty)) {
			difference = targetProperty;
		}
		else if (targetProperty.equals(originProperty)) {
			difference = null;
		}
		else {
			difference = targetProperty;
		}
		return difference;
	}

	/**
	 * 指定されたリソースをプロパティとしてロードします。<br>
	 * @param values インストーラ共通データオブジェクト。
	 * @param resourceKey 取得するリソースの位置を取得するためのキー文字列。
	 * @return resourceKeyが指し示すパスのリソースから
	 *          生成されたプロパティを戻します。
	 * @throws InstallerException リソース情報取得に失敗した場合。
	 */
	public static Properties load(InstallerValues values, String resourceKey)
			throws InstallerException {
		// メソッド開始タグ
		LoggerUtil.methodHead(_logger, "load(" + resourceKey + ")");

		// このメソッドの戻り値
		Properties properties = null;
		// リソースからの入力ストリーム
		InputStream stream = null;
		// 成功フラグ（成功した場合にtrueに変更）
		boolean successFlag = false;
		try {
			stream = values.getResourceKeyStream(resourceKey);
			properties = new Properties();
			properties.load(stream);
			successFlag = true;
		}
		catch (InstallerException e) {
			throw e;
		}
		catch (Exception e) {
			LoggerUtil.throwable(_logger, e);
			throw new InstallerException(e);
		}
		finally {
			InstallerFileUtil.close(stream, successFlag);
		}
		// メソッド終了タグ
		LoggerUtil.methodTail(_logger, "load(" + resourceKey + ")");
		return properties;
	}

	/**
	 * プロパティファイルを上書きします。<br>
	 * @param values インストーラ共通データオブジェクト。
	 * @param propertiesValue 上書きする項目について取得できるデータ。
	 * @throws InstallerException プロパティファイルの取得・書込に失敗した場合。
	 */
	public static void overwriteProperties(
			InstallerValues values,
			InstallerOverwritePropertiesValue propertiesValue)
			throws InstallerException {
		////////////////////////////////////////////////////////////////////////
		// メソッド開始タグ
		LoggerUtil.methodHead(_logger,
			"overwriteProperties(InstallerValues," + propertiesValue + ")");
		////////////////////////////////////////////////////////////////////////
		// プロパティファイルの上書き
		// =====================================================================
		// プロパティファイルを取得するためにキー文字列
		String targetFileKey = propertiesValue.getTargetFileKey();
		// 上書きするプロパティ
		Properties overwrittenProperties
			= propertiesValue.getOverwrittenProperties(values);
		// プロパティファイルのエンコード
		String targetFileEncode
			= values.getResourceKeyEncode(targetFileKey);
		//  プロパティの更新
		overwritePropertiesFile(values,
			overwrittenProperties, targetFileKey, targetFileEncode, true);
		////////////////////////////////////////////////////////////////////////
		// プロパティファイル（平文）の上書き
		// =====================================================================
		// プロパティファイル(Native)を取得するためにキー文字列
		String nativeFileKey = propertiesValue.getTargetNativeFileKey();
		if (StringUtil.isEmpty(nativeFileKey) == false) {
			// プロパティファイルのエンコード
			String nativeFileEncode
				= values.getResourceKeyEncode(nativeFileKey);
			//  プロパティの更新
			overwritePropertiesFile(values,
				overwrittenProperties, nativeFileKey, nativeFileEncode, false);
		}
		////////////////////////////////////////////////////////////////////////
		// メソッド終了タグ
		LoggerUtil.methodTail(_logger,
			"overwriteProperties(InstallerValues," + propertiesValue + ")");
	}


	/**
	 * プロパティファイルを上書きします。<br>
	 * @param values インストーラ共通データオブジェクト。
	 * @param overwrittenProperties 上書きするプロパティ。
	 * @param targetFileKey 更新するプロパティファイルを取得するためのキー文字列。
	 * @param fileEncode 更新するプロパティファイルのエンコード。
	 * @param unicodeEscape プロパティ文字列をUNICDEエスケープ処理する場合は、
	 *         trueを、エスケープ処理しない場合はfalseを渡します。
	 * @throws InstallerException プロパティファイルの取得・書込に失敗した場合。
	 */
	private static void overwritePropertiesFile(
			InstallerValues values,
			Properties overwrittenProperties,
			String targetFileKey,
			String fileEncode,
			boolean unicodeEscape)
			throws InstallerException {
		// メソッド開始タグ
		LoggerUtil.methodHead(_logger,
			"overwritePropertiesFile(InstallerValues,Properties,"
				+ targetFileKey + "," + fileEncode + ")");
		// メソッド本体
		BufferedReader reader = null;
		BufferedWriter writer = null;
		boolean successFlag = false;
		try {
			// =================================================================
			// 書き換え入力用テンポラリ・ファイルと
			// 書き換え出力用テンポラリ・ファイルのパスを設定。
			File tempDir = values.getInstallerTempDir();
			File tempInputFile = File.createTempFile(
				targetFileKey + "_IN_", ".properties", tempDir);
			File tempOutputFile = File.createTempFile(
				targetFileKey + "_OUT_", ".properties", tempDir);
			// =================================================================
			// 対象ファイルが存在すれば、入力用テンポラリ・ファイルにコピーする
			File targetFile = values.getFileOnSystem(targetFileKey);
			if (targetFile.exists()) {
				InstallerFileUtil.copyFile(targetFile, tempInputFile);
			}
			// =================================================================
			// 対象ファイルが存在しない場合は、リソース（原本）の内容を
			// 入力用テンポラリ・ファイルにコピーする
			else {
				InstallerFileUtil.loadFile(
					values, tempInputFile, targetFileKey);
			}
			// =================================================================
			// 入力用テンポラリ・ファイルから１行ずつ読みながら、
			// 出力用テンポラリ・ファイルに出力する
			reader = new BufferedReader(
				new InputStreamReader(
					new FileInputStream(tempInputFile), fileEncode));
			writer = new BufferedWriter(
				new OutputStreamWriter(
					new FileOutputStream(tempOutputFile), fileEncode));
			// ワーク・プロパティ
			Properties workProperties
				= (Properties)overwrittenProperties.clone();
			while (true) {
				String line = null;
				line = reader.readLine();
				if (line == null) {
					break;
				}
				String trimmedLine = line.trim();
				// コメント行の判断
				// コメント行ならばそのまま出力
				if (trimmedLine.startsWith("#")
						|| trimmedLine.startsWith("!")) {
					writer.write(line);
					writer.newLine();
					continue;
				}
				// プロパティ値を設定している行か判断
				int boudaryPos = trimmedLine.indexOf('=');
				if (boudaryPos < 0) {
					boudaryPos = trimmedLine.indexOf(':');
				}
				// プロパティ設定行でなければ、そのまま出力
				if (boudaryPos <= 0) {
					writer.write(line);
					writer.newLine();
					continue;
				}
				// 設定されているプロパティキー文字列のプロパティ値を取得
				String propertyName = trimmedLine.substring(0, boudaryPos);
				propertyName = propertyName.trim();
				// 取得したプロパティが更新対象であるか確認
				String newPropertyValue
					= workProperties.getProperty(propertyName);
				// 更新対象でなければ、そのまま出力
				if (newPropertyValue == null) {
					writer.write(line);
					writer.newLine();
					continue;
				}
				// 更新する値が存在すれば、更新後の値に変更して出力
				if (unicodeEscape) {
					propertyName
						= escapeUnicodeString(propertyName);
					newPropertyValue
						= escapeUnicodeString(newPropertyValue);
				}
				writer.write(propertyName);
				writer.write("=");
				writer.write(newPropertyValue);
				writer.newLine();
				// 更新候補から削除する
				workProperties.remove(propertyName);
			}
			// =================================================================
			// 出力していない更新プロパティがあれば出力
			for (Iterator it = workProperties.keySet().iterator();
					it.hasNext();) {
				String propertyName = (String)it.next();
				String propertyValue
					= workProperties.getProperty(propertyName);

				if (unicodeEscape) {
					propertyName
						= escapeUnicodeString(propertyName);
					propertyValue
						= escapeUnicodeString(propertyValue);
				}
				writer.write(propertyName);
				writer.write("=");
				writer.write(propertyValue);
				writer.newLine();
			}
			// 直後のファイルのコピーするのでクローズ
			writer.close();
			// =================================================================
			// 出力用プロパティ・ファイルの内容を対象ファイルにコピーする
			InstallerFileUtil.copyFile(tempOutputFile, targetFile);
			// =================================================================
			// tryブロックの終端まで到達したら成功とみなす。
			successFlag = true;
		}
		catch (InstallerException e) {
			throw e;
		}
		catch (Exception e) {
			LoggerUtil.throwable(_logger, e);
			throw new InstallerException(e);
		}
		finally {
			InstallerFileUtil.close(reader, writer, successFlag);
		}
		// メソッド終了タグ
		LoggerUtil.methodTail(_logger,
			"overwritePropertiesFile(InstallerValues,Properties,"
				+ targetFileKey + "," + fileEncode + ")");
	}

	/**
	 * 渡された文字列をプロパティファイルに出力するために文字列に変換します。
	 * @param value 変換対象の文字列。
	 * @return 変換後の文字列。
	 */
	private static String escapeUnicodeString(String value) {
		String convertedValue = null;
		if (value == null) {
			convertedValue = "";
		}
		else {
			StringBuffer buffer = new StringBuffer();
			// 一文字ずつ、エスケープの要否を判断して、連結する。
			for (int i = 0; i < value.length(); i++) {
				char c = value.charAt(i);
				// バックスラッシュ
				if (c == '\\') {
					buffer.append("\\\\");
				}
				// タブ
				else if (c == '\t') {
					buffer.append("\\t");
				}
				// 改行
				else if (c == '\n') {
					buffer.append("\\n");
				}
				// 復帰
				else if (c == '\r') {
					buffer.append("\\r");
				}
				// ASCII文字以外の場合は、
				// \\uXXXX形式のUNICODEエスケープしてから連結
				else if (c < '\u0020' || '\u007E' < c) {
					buffer.append("\\u");
					// 16進文字列を生成、4桁0パディングを施して連結。
					String escaped = Integer.toHexString(c);
					switch (escaped.length()) {
						case 0:
							buffer.append("0000");
							break;
						case 1:
							buffer.append("000");
							buffer.append(escaped);
							break;
						case 2:
							buffer.append("00");
							buffer.append(escaped);
							break;
						case 3:
							buffer.append("0");
							buffer.append(escaped);
							break;
						default:
							buffer.append(escaped);
							break;
					}
				}
				// 通常のASCII文字はそのまま連結
				else {
					buffer.append(c);
				}
			}
			convertedValue = buffer.toString();
		}
		return convertedValue;
	}
}
