/*
 * Copyright (c) 2009 The openGion Project.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.opengion.fukurou.util;

import java.io.File;
// import java.util.Map;
// import java.util.HashMap;
import java.util.concurrent.ConcurrentMap;							// 6.4.3.3 (2016/03/04)
import java.util.concurrent.ConcurrentHashMap;				// 6.4.3.1 (2016/02/12) refactoring
import java.util.Locale ;
import java.util.Set;

import org.opengion.fukurou.system.ThrowUtil;				// 6.4.2.0 (2016/01/29) package変更 fukurou.util → fukurou.system

/**
 * FileMap は、ファイルを読み取って、キー情報から、ファイルへのリンクを作成するための
 * 情報を返します。
 * ファイルそのものは、指定のディレクトリをすべて読み取り、拡張子以外の部分を、キーとして
 * 登録します。(キーは大文字に統一されます。)
 * 実際のファイルの拡張子は、リンク作成時の処理で付与されます。
 * 例えば、HELPファイルを、XXXX.html や、XXXX.htm 、XXXX.pdf など、色々な形態で作成した
 * 場合でも、キーとしては、XXXX で存在チェックをかけることができるようになります。
 *
 * ファイルは、一旦すべて読み取ってメモリ上で管理されます。
 * ディレクトリの再読取が必要な場合は、オブジェクトを再作成する必要があります。
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
// public final class FileMap {
public final class FileMap implements Cleanable {
	/** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。  */
//	private final Map<String,String> map = new HashMap<>();
	private final ConcurrentMap<String,String> fMap = new ConcurrentHashMap<>();				// 6.4.3.1 (2016/02/12) 変数名も変えておきます。

//	private boolean initFlag	;			// 6.3.9.0 (2015/11/06)

	/**
	 * デフォルトコンストラクター
	 *
	 * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
	 */
	public FileMap() {
		super();		// これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
	}

	/**
	 * 読み取るディレクトリを指定して、ファイルマップを構築します。
	 *
	 * このディレクトリは、OSに対する物理アドレスになります。
	 *
	 * @og.rev 5.5.4.2 (2012/07/13) makeFileMap() を直接コンストラクターとして使用
	 * @og.rev 6.3.8.4 (2015/10/09) 別のコンストラクターを呼ぶようにします。
	 * @og.rev 6.3.8.5 (2015/10/16) コンストラクターで、Exception を throw しないようにします。
	 * @og.rev 6.3.9.0 (2015/11/06) コンストラクターを止めて、初期化メソッドに変更する。
	 *
	 * @param  dir ディレクトリ
//	 * @throws IllegalArgumentException 引数の dir が存在しないか、ディレクトリ出ない場合。
	 */
//	public FileMap( final String dir ) throws IllegalArgumentException {
//	public FileMap( final String dir ) {
//		this( dir , null , null );
	public void init( final String dir ) {
		init( dir , null , null );

//		final File directory = new File( dir );
//		if( ! directory.exists() ) {
//			final String errMsg = "指定のディレクトリは、存在しません。[" + directory + "]";
//			throw new IllegalArgumentException( errMsg );
//		}
//
//		if( ! directory.isDirectory() ) {
//			final String errMsg = "指定のキーは、ディレクトリではありません。[" + directory + "]";
//			throw new IllegalArgumentException( errMsg );
//		}
//
//		for( final String fname : directory.list() ) {
//			final String upkey  = fname.toUpperCase( Locale.JAPAN ) ;
//
//			final int idx = upkey.lastIndexOf( '.' );
//			if( idx >= 0 ) {
//				map.put( upkey.substring( 0,idx ), fname );
//			}
//			else {
//				map.put( upkey, fname );
//			}
//		}
	}

	/**
	 * 読み取るディレクトリを指定して、ファイルマップを構築します。
	 *
	 * このディレクトリは、OSに対する物理アドレスになります。
	 *
	 * @og.rev 5.5.4.2 (2012/07/13) makeFileMap() を直接コンストラクターとして使用
	 * @og.rev 6.3.8.4 (2015/10/09) 別のコンストラクターを呼ぶようにします。
	 * @og.rev 6.3.8.5 (2015/10/16) コンストラクターで、Exception を throw しないようにします。
	 * @og.rev 6.3.9.0 (2015/11/06) コンストラクターを止めて、初期化メソッドに変更する。
	 *
	 * @param  dir ディレクトリ
	 * @param  path ファイル名に付与するパス文字列
//	 * @throws IllegalArgumentException 引数の dir が存在しないか、ディレクトリ出ない場合。
	 */
//	public FileMap( final String dir , final String path ) throws IllegalArgumentException {
//	public FileMap( final String dir , final String path ) {
//		this( dir , path , null );
	public void init( final String dir , final String path ) {
		init( dir , path , null );

//		final File directory = new File( dir );
//		if( ! directory.exists() ) {
//			final String errMsg = "指定のディレクトリは、存在しません。[" + directory + "]";
//			throw new IllegalArgumentException( errMsg );
//		}
//
//		if( ! directory.isDirectory() ) {
//			final String errMsg = "指定のキーは、ディレクトリではありません。[" + directory + "]";
//			throw new IllegalArgumentException( errMsg );
//		}
//
//		for( final String fname : directory.list() ) {
//			final String upkey  = fname.toUpperCase( Locale.JAPAN ) ;
//
//			final int idx = upkey.lastIndexOf( '.' );
//			if( idx >= 0 ) {
//				map.put( upkey.substring( 0,idx ), fname );
//			}
//			else {
//				map.put( upkey, fname );
//			}
//		}
	}

	/**
	 * すでに読み取った Set オブジェクトを指定して、ファイルマップを構築します。
	 *
	 * これは、ServletContext を利用した、META-INF/resources からの読み取り対応になります。
	 * 一覧を取得するのは、ServletContext 関連の実装が必要になるため、fukurou では
	 * java の一般的なオブジェクトである Set を処理するだけとします。
	 *
	 * ファイル名は、dir を削除した残りで構築します。フォルダ階層を含みます。
	 * Mapのキーは、フォルダ階層を含まない、ファイル名のみとします。
	 * つまり、フォルダ階層を持ってリソースを用意しておいても、キーとしては、
	 * ファイル名のみを使用します。
	 *
	 * @og.rev 5.5.4.2 (2012/07/13) 新規作成
	 * @og.rev 6.3.8.4 (2015/10/09) 別のコンストラクターを呼ぶようにします。
	 * @og.rev 6.3.8.5 (2015/10/16) コンストラクターで、Exception を throw しないようにします。
	 * @og.rev 6.3.9.0 (2015/11/06) コンストラクターを止めて、初期化メソッドに変更する。
	 *
	 * @param  dir ディレクトリ
	 * @param  resourcePaths リソースパス
//	 * @throws IllegalArgumentException 引数の dir や、resourcePaths が、null の場合
	 */
//	public FileMap( final String dir , final Set<?> resourcePaths ) throws IllegalArgumentException {
//	public FileMap( final String dir , final Set<?> resourcePaths ) throws IllegalArgumentException {
//	public FileMap( final String dir , final Set<?> resourcePaths ) {
//		this( dir , null , resourcePaths );
	public void init( final String dir , final Set<?> resourcePaths ) {
		init( dir , null , resourcePaths );

//		if( resourcePaths == null || dir == null ) {
//			final String errMsg = "指定のリソースは、存在しません。[" + dir + "]";
//			throw new IllegalArgumentException( errMsg );
//		}
//
//		final int len = dir.length() ;
//
//		for( final Object path : resourcePaths ) {
//			final String fname  = String.valueOf( path ).substring( len );	// ファイル名
//			final String upkey  = fname.toUpperCase( Locale.JAPAN ) ;			// 大文字化されたファイル名(Mapのキー)
//
//			final int idx = fname.lastIndexOf( '.' );
//			if( idx >= 0 ) {
//				map.put( upkey.substring( 0,idx ), fname );
//			}
//			else {
//				map.put( upkey, fname );
//			}
//		}
	}

	/**
	 * すでに読み取った Set オブジェクトを指定して、ファイルマップを構築します。
	 *
	 * これは、ServletContext を利用した、META-INF/resources からの読み取り対応になります。
	 * 一覧を取得するのは、ServletContext 関連の実装が必要になるため、fukurou では
	 * java の一般的なオブジェクトである Set を処理するだけとします。
	 *
	 * ファイル名は、dir を削除した残りで構築します。フォルダ階層を含みます。
	 * Mapのキーは、フォルダ階層を含まない、ファイル名のみとします。
	 * つまり、フォルダ階層を持ってリソースを用意しておいても、キーとしては、
	 * ファイル名のみを使用します。
	 *
	 * @og.rev 5.5.4.2 (2012/07/13) 新規作成
	 * @og.rev 6.3.8.4 (2015/10/09) 別のコンストラクターを呼ぶようにします。
	 * @og.rev 6.3.8.5 (2015/10/16) コンストラクターで、Exception を throw しないようにします。
	 * @og.rev 6.3.9.0 (2015/11/06) null になっている可能性があるメソッドの戻り値のnullチェックを追加。
	 * @og.rev 6.3.9.0 (2015/11/06) コンストラクターを止めて、初期化メソッドに変更する。
	 * @og.rev 6.4.2.0 (2016/01/29) StringUtil にあったメソッドを移動するとともに、メソッド名を、ogThrowMsgPrint → ogThrowMsgPrint に変更。
	 * @og.rev 6.4.3.2 (2016/02/19) 指定のフォルダが存在しない場合、作成します。
	 *
	 * @param  dir ディレクトリ
	 * @param  path ファイル名に付与するパス文字列
	 * @param  resourcePaths リソースパス
//	 * @throws IllegalArgumentException 引数の dir や、resourcePaths が、null の場合
	 */
//	public FileMap( final String dir , final Set<?> resourcePaths ) throws IllegalArgumentException {
//	public FileMap( final String dir , final String path , final Set<?> resourcePaths ) throws IllegalArgumentException {
//	public FileMap( final String dir , final String path , final Set<?> resourcePaths ) {
	public void init( final String dir , final String path , final Set<?> resourcePaths ) {
		// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
//		if( resourcePaths != null ) {
//			final int len = dir.length() ;
//
//			for( final Object rpath : resourcePaths ) {
//				final String fname = String.valueOf( rpath ).substring( len );	// ファイル名
//				dataSet( fname , path );
//			}
//		}
//		else {
		if( resourcePaths == null ) {
			final File directory = new File( dir );
			if( ! directory.exists() ) {
				final String errMsg = "指定のディレクトリは、存在しません。[" + directory + "]";
				// 6.3.8.5 (2015/10/16) コンストラクターで、Exception を throw しないようにします。
//				throw new IllegalArgumentException( errMsg );
//				StringUtil.ogErrMsgPrint( errMsg , new RuntimeException() );
		//		System.err.println( ThrowUtil.ogThrowMsg( errMsg ) );			// 6.3.8.5 (2015/10/16) ここではエラーにしない。
				System.out.println( errMsg );
				// 6.4.3.2 (2016/02/19) 指定のフォルダが存在しない場合、作成します。
				if( directory.mkdirs() ) {
					final String errMsg2 = "指定のディレクトリを自動作成しました。[" + directory + "]";
					System.out.println( errMsg2 );
				}
				else {
					final String errMsg3 = "指定のディレクトリの自動作成に失敗しました。[" + directory + "]";
					System.err.println( ThrowUtil.ogThrowMsg( errMsg3 ) );
				}
				return ;
			}

			if( ! directory.isDirectory() ) {
				final String errMsg = "指定のキーは、ディレクトリではありません。[" + directory + "]";
				// 6.3.8.5 (2015/10/16) コンストラクターで、Exception を throw しないようにします。
//				throw new IllegalArgumentException( errMsg );
//				StringUtil.ogErrMsgPrint( errMsg , new RuntimeException() );
				System.err.println( ThrowUtil.ogThrowMsg( errMsg ) );
				return ;
			}
			// 6.3.8.4 (2015/10/09) ﾌｧｲﾙのみ取り込む
			// 6.3.9.0 (2015/11/06) null になっている可能性があるメソッドの戻り値のnullチェックを追加。
			final File[] files = directory.listFiles();
			if( files != null ) {
				for( final File file : files ) {
					if( file != null && file.isFile() ) {
						dataSet( file.getName() , path );
					}
				}
			}
//			for( final File file : directory.listFiles() ) {
//				if( file.isFile() ) {
//					final String fname = file.getName();
//					dataSet( fname , path );
//				}
//			}
		}
		else {
			final int len = dir.length() ;

			for( final Object rpath : resourcePaths ) {
				final String fname = String.valueOf( rpath ).substring( len );	// ファイル名
				dataSet( fname , path );
			}
		}

//		initFlag = true;			// 6.3.9.0 (2015/11/06)
	}

	/**
	 * ファイルマップを構築する内部処理。
	 *
	 * これは、ServletContext を利用した、META-INF/resources からの読み取り対応と、
	 * 通常のフォルダスキャンの読み取りの共通処理をまとめた目疎度です。
	 *
	 * @og.rev 6.3.8.4 (2015/10/09) 新規作成
	 * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。変数名も変えておきます。
	 *
	 * @param  fname ファイル名
	 * @param  path  ファイル名に付与するパス文字列
	 */
	private void dataSet( final String fname , final String path ) {
		final String upkey = fname.toUpperCase( Locale.JAPAN ) ;
		String tmpName = fname;

		// path が、nullやゼロ文字列以外の場合は、最後の文字を判定して、ファイル名に連結します。
		if( path != null && !path.isEmpty() ) {
			final char ch = path.charAt( path.length()-1 ) ;
			if( ch == '/' || ch == '\\' ) {
				tmpName = path + fname;
			}
			else {
				tmpName = path + '/' + fname;
			}
		}

		// 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。変数名も変えておきます。
		final int idx = upkey.lastIndexOf( '.' );
		if( idx >= 0 ) {
//			map.put( upkey.substring( 0,idx ), tmpName );
			fMap.put( upkey.substring( 0,idx ), tmpName );
		}
		else {
//			map.put( upkey, tmpName );
			fMap.put( upkey, tmpName );
		}
	}

	/**
	 * 指定のキーのファイルが存在しているかどうかを返します。
	 * 存在している場合は、true , 存在していない場合は、false になります。
	 *
	 * @og.rev 6.3.8.5 (2015/10/16) Exception を throw しないようにします。
	 * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
	 *
	 * @param   key 指定のキー
	 *
	 * @return	存在しているかどうか(true:存在する/false:存在しない)
//	 * @throws  IllegalArgumentException キーが指定されていません。
	 */
	public boolean exists( final String key ) {
//		if( key == null ) {
//			final String errMsg = "キーが指定されていません。" ;
//			throw new IllegalArgumentException( errMsg );
//		}

//		return map.containsKey( key.toUpperCase( Locale.JAPAN ) );

		// 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。変数名も変えておきます。
//		return key != null && map.containsKey( key.toUpperCase( Locale.JAPAN ) );
		return key != null && fMap.containsKey( key.toUpperCase( Locale.JAPAN ) );
	}

	/**
	 * キーに対応したファイル名を返します。
	 * 指定のキーに対するファイル名が存在しない場合は、null を返します。
	 *
	 * 引数を可変長引数にして、前から順番にMapを調べます。最初に、null でない
	 * 値を返します。最後まで一致しなければ、null を返します。
	 * 引数のキーが、nullや、ゼロ配列の場合も、nullになります。
	 *
	 * @og.rev 6.3.8.4 (2015/10/09) FileMap のコンストラクタ変更に伴う対応。
	 * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
	 *
	 * @param   keys 指定のキー配列(可変長引数)
	 *
	 * @return	ファイル名(ディレクトリパスは含まず)、存在しない場合は、null
	 */
	public String getFilename( final String... keys ) {
		if( keys != null ) {
			for( final String key : keys ) {
				// 6.3.8.4 (2015/10/09) 最初に見つけた値を返す。
				if( key != null ) {
					// 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。変数名も変えておきます。
//					final String rtn = map.get( key.toUpperCase( Locale.JAPAN ) );
					final String rtn = fMap.get( key.toUpperCase( Locale.JAPAN ) );
					if( rtn != null ) { return rtn; }
				}
			}
		}
		return null;
	}
//	public String getFilename( final String key ) {
//		if( key == null ) {
//			return null ;
//	//	final String errMsg = "キーが指定されていません。" ;
//	//		throw new IllegalArgumentException( errMsg );
//		}
//
//		return map.get( key.toUpperCase( Locale.JAPAN ) );
//	}

	/**
	 * 初期化が完了しているかどうかを、返します。
	 * 完了している場合は、true を返します。未完了、または、clear() 実行後は、falseです。
	 *
	 * インスタンスは、init処理が完了するまでは、false が返る為、簡易的な同期処理は
	 * 行われています。
	 * (内部のMapへの書き込みは、init処理でのみ行われます。)
	 *
	 * @og.rev 6.3.9.0 (2015/11/06) 新規作成。
	 * @og.rev 6.4.3.2 (2016/02/19) initFlagを廃止し、直接 Mapが空かどうかで判定します。。
	 *
	 * @return	初期化が完了していればtrue
	 */
//	public boolean isInit() { return initFlag; }
	public boolean isInit() { return !fMap.isEmpty(); }

	/**
	 * 初期化(クリア)します。
	 *
	 * @og.rev 6.3.9.0 (2015/11/06) 新規作成。
	 * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
	 */
	@Override
	public void clear() {
//		initFlag = false;
		// 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。変数名も変えておきます。
//		map.clear();
		fMap.clear();
	}
}
