/*
 * 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 org.opengion.fukurou.system.OgRuntimeException ;		// 6.4.2.0 (2016/01/29)
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.Collections;
import java.util.List;

import java.nio.channels.FileChannel;
import java.nio.file.Files;										// 6.2.0.0 (2015/02/27)
import java.nio.charset.Charset;								// 6.2.0.0 (2015/02/27)
import org.opengion.fukurou.system.HybsConst;					// 6.4.5.2 (2016/05/06)

import static org.opengion.fukurou.system.HybsConst.CR;			// 6.1.0.0 (2014/12/26) refactoring
import org.opengion.fukurou.system.Closer;						// 6.4.2.0 (2016/01/29) package変更 fukurou.util → fukurou.system
import org.opengion.fukurou.system.LogWriter;					// 6.4.2.0 (2016/01/29) package変更 fukurou.util → fukurou.system
import org.opengion.fukurou.system.ThrowUtil;					// 6.4.2.0 (2016/01/29) package変更 fukurou.util → fukurou.system

/**
 * FileUtil.java は、共通的に使用される File関連メソッドを集約した、クラスです。
 *
 * 全変数は、public static final 宣言されており、全メソッドは、public static synchronized 宣言されています。
 *
 * @og.group ユーティリティ
 *
 * @version  4.0
 * @author	 Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public final class FileUtil {
	private static final NonClosePrintWriter OUT_WRITER = new NonClosePrintWriter( System.out );		// 6.4.1.1 (2016/01/16) outWriter → OUT_WRITER refactoring
	private static final NonClosePrintWriter ERR_WRITER = new NonClosePrintWriter( System.err );		// 6.4.1.1 (2016/01/16) errWriter → ERR_WRITER refactoring

	/** 5.6.1.2 (2013/02/22) UNIX系のファイル名を表すセパレータ文字	*/

	/** 5.6.1.2 (2013/02/22) Windwos系のファイル名を表すセパレータ文字	*/

	/** 5.6.1.2 (2013/02/22) ファイルの拡張子の区切りを表す文字	*/
	public static final char EXTENSION_SEPARATOR = '.';

	private static final byte B_CR = (byte)0x0d ;	// '\r'
	private static final byte B_LF = (byte)0x0a ;	// '\n'
	private static final int  BUFSIZE = 8192 ;		// 5.1.6.0 (2010/05/01)

	/**
	 * すべてが staticメソッドなので、コンストラクタを呼び出さなくしておきます。
	 *
	 */
	private FileUtil() {}

	/**
	 * Fileオブジェクトとエンコードより PrintWriterオブジェクトを作成します。
	 *
	 * @param	file	出力するファイルオブジェクト
	 * @param	encode	ファイルのエンコード
	 *
	 * @return	PrintWriterオブジェクト
	 * @throws RuntimeException 何らかのエラーが発生した場合
	 * @og.rtnNotNull
	 */
	public static PrintWriter getPrintWriter( final File file,final String encode ) {
		return getPrintWriter( file,encode,false );
	}

	/**
	 * Fileオブジェクトとエンコードより PrintWriterオブジェクトを作成します。
	 *
	 * @param	file	出力するファイルオブジェクト
	 * @param	encode	ファイルのエンコード
	 * @param	append	ファイルを追加モード(true)にするかどうか
	 *
	 * @return	PrintWriterオブジェクト
	 * @throws RuntimeException 何らかのエラーが発生した場合
	 * @og.rtnNotNull
	 */
	public static PrintWriter getPrintWriter( final File file,final String encode,final boolean append ) {
		final PrintWriter writer ;

		try {
			writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(
							new FileOutputStream(file,append) ,encode )));
		}
		catch( final UnsupportedEncodingException ex ) {
			final String errMsg = "指定されたエンコーディングがサポートされていません。" + CR
							+ ex.getMessage() + CR
							+ "File=[" + file + " , encode=[" + encode + "]" ;
			throw new OgRuntimeException( errMsg,ex );
		}
		catch( final FileNotFoundException ex ) {		// 3.6.1.0 (2005/01/05)
			final String errMsg = "ファイル名がオープン出来ませんでした。" + CR
							+ ex.getMessage() + CR
							+ "File=[" + file + " , encode=[" + encode + "]" ;
			throw new OgRuntimeException( errMsg,ex );
		}

		return writer ;
	}

	/**
	 * ファイル名より、PrintWriterオブジェクトを作成する簡易メソッドです。
	 *
	 * これは、ファイル名は、フルパスで、追加モードで、UTF-8 エンコードの
	 * ログファイルを出力する場合に使用します。
	 * また、ファイル名に、"System.out" と、"System.err" を指定できます。
	 * その場合は、標準出力、または、標準エラー出力に出力されます。
	 * "System.out" と、"System.err" を指定した場合は、NonClosePrintWriter
	 * オブジェクトが返されます。これは、close() 処理が呼ばれても、何もしない
	 * クラスです。また、常に内部キャッシュの同じオブジェクトが返されます。
	 *
	 * @param	file	出力するファイル名
	 *
	 * @return	PrintWriterオブジェクト
	 * @throws RuntimeException 何らかのエラーが発生した場合
	 * @throws IllegalArgumentException ファイル名が null の場合
	 */
	public static PrintWriter getLogWriter( final String file ) {
		if( file == null ) {
			final String errMsg = "ファイル名に、null は指定できません。";
			throw new IllegalArgumentException( errMsg );
		}

		final PrintWriter writer ;
		if( "System.out".equalsIgnoreCase( file ) ) {
			writer = OUT_WRITER ;
		}
		else if( "System.err".equalsIgnoreCase( file ) ) {
			writer = ERR_WRITER ;
		}
		else {
			writer = getPrintWriter( new File( file ),"UTF-8",true );
		}

		return writer ;
	}

	/**
	 * OutputStreamとエンコードより PrintWriterオブジェクトを作成します。
	 *
	 * @og.rev 5.5.2.0 (2012/05/01) 新規追加
	 *
	 * @param	os		利用するOutputStream
	 * @param	encode	ファイルのエンコード
	 *
	 * @return	PrintWriterオブジェクト
	 * @throws RuntimeException 何らかのエラーが発生した場合
	 */
	public static PrintWriter getPrintWriter( final OutputStream os,final String encode ) {
		final PrintWriter writer ;

		try {
			writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter( os ,encode )));
		}
		catch( final UnsupportedEncodingException ex ) {
			final String errMsg = "指定されたエンコーディングがサポートされていません。" + CR
							+ ex.getMessage() + CR
							+ "encode=[" + encode + "]" ;
			throw new OgRuntimeException( errMsg,ex );
		}
		return writer ;
	}

	/**
	 * PrintWriter を継承した、JspWriterなどの Writer 用のクラスを定義します。
	 *
	 * 例えば、JspWriterなどの JSP/Servlet等のフレームワークで使用される
	 * Writer では、flush や close 処理は、フレームワーク内で行われます。
	 * その場合、通常のファイルと同じ用に、flush や close をアプリケーション側で
	 * 行うと、内部処理的に不整合が発生したり、最悪の場合エラーになります。
	 * このクラスは、NonFlushPrintWriter クラスのオブジェクトを返します。
	 * これは、通常の、new PrintWriter( Writer ) で、求めるのと、ほとんど同様の
	 * 処理を行いますが、close() と flush() メソッドが呼ばれても、何もしません。
	 *
	 * @param	writer	出力するWriteオブジェクト(NonFlushPrintWriterクラス)
	 *
	 * @return	PrintWriterオブジェクト
	 * @og.rtnNotNull
	 */
	public static PrintWriter getNonFlushPrintWriter( final Writer writer ) {
		return new NonFlushPrintWriter( writer );
	}

	/**
	 * Fileオブジェクトとエンコードより BufferedReaderオブジェクトを作成します。
	 *
	 * これは、java 1.7 以降でしか使えませんが、FilesとPaths を使用した BufferedReader
	 * オブジェクトを返します。
	 * encode は、java.nio.charset.Charset になる為、従来のコードと異なるかも知れませんが、
	 * 日本語関係の判定をより正確に行う事が可能になります。(Windows-31J と UTF-8の判別など)
	 *
	 * @og.rev 6.2.0.0 (2015/02/27) java.nio.file.Files と、Paths を使用するように変更
	 *
	 * @param	file	入力するファイルオブジェクト
	 * @param	encode	ファイルのエンコード(java.nio.charset.Charset)
	 *
	 * @return	BufferedReaderオブジェクト
	 * @throws RuntimeException 何らかのエラーが発生した場合
	 * @og.rtnNotNull
	 */
	public static BufferedReader getBufferedReader( final File file,final String encode ) {
		final BufferedReader reader ;

		try {
			reader = Files.newBufferedReader( file.toPath() , Charset.forName( encode ) );
		}
		catch( final IOException ex ) {
			final String errMsg = "ファイルのオープン中に入出力エラーが発生しました。" + CR
							+ ex.getMessage() + CR
							+ "File=[" + file + "] , encode=[" + encode + "]" ;
			throw new OgRuntimeException( errMsg,ex );
		}
		catch( final RuntimeException ex ) {
			final String errMsg = "指定された文字セットが不正か、現在のJava仮想マシンでは利用できません。" + CR
							+ ex.getMessage() + CR
							+ "File=[" + file + "] , encode=[" + encode + "]" ;
			throw new OgRuntimeException( errMsg,ex );
		}

		return reader ;
	}

	/**
	 * 指定のファイル名が、実際に存在しているかどうかをチェックします。
	 * 存在しない場合は、２秒毎に、３回確認します。
	 * それでも存在しない場合は、エラーを返します。
	 * return されるFileオブジェクトは、正規の形式(CanonicalFile)です。
	 *
	 * @param	dir			フォルダ名
	 * @param	filename	ファイル名
	 *
	 * @return	存在チェック(なければ null/あれば、CanonicalFile)
	 */
	public static File checkFile( final String dir, final String filename ) {
		return checkFile( dir,filename,3 );
	}

	/**
	 * 指定のファイル名が、実際に存在しているかどうかをチェックします。
	 * 存在しない場合は、２秒毎に、指定の回数分確認します。
	 * それでも存在しない場合は、エラーを返します。
	 * return されるFileオブジェクトは、正規の形式(CanonicalFile)です。
	 *
	 * @param	dir			フォルダ名
	 * @param	filename	ファイル名
	 * @param	count		回数指定
	 *
	 * @return	存在チェック(なければ null/あれば、CanonicalFile)
	 */
	public static File checkFile( final String dir, final String filename,final int count ) {
		File file = null;

		int cnt = count;
		while( cnt > 0 ) {
			file = new File( dir,filename );
			if( file.exists() ) { break; }
			else {
				if( cnt == 1 ) { return null; }		// 残り１回の場合は、２秒待機せずに即抜ける。
				try { Thread.sleep( 2000 );	}	// ２秒待機
				catch( final InterruptedException ex ) {
					System.out.println( "InterruptedException" );
				}
				System.out.println();
				System.out.print( "CHECK File Error! CNT=" + cnt );
				System.out.print( " File=" + file.getAbsolutePath() );
			}
			cnt--;
		}

		// ファイルの正式パス名の取得
		try {
			return file.getCanonicalFile() ;
		}
		catch( final IOException ex ) {
			final String errMsg = "ファイルの正式パス名が取得できません。[" + file.getAbsolutePath() + "]";
			throw new OgRuntimeException( errMsg,ex );
		}
	}

	/**
	 * ファイルのバイナリコピーを行います。
	 *
	 * copy( File,File,false ) を呼び出します。
	 *
	 * @og.rev 5.1.6.0 (2010/05/01) 戻り値に、true/false 指定します。
	 *
	 * @param	fromFile	コピー元ファイル名
	 * @param	toFile		コピー先ファイル名
	 *
	 * @return	バイナリコピーが正常に終了したかどうか[true:成功/false:失敗]
	 * @see		#copy( File,File,boolean )
	 */
	public static boolean copy( final String fromFile,final String toFile ) {
		return copy( new File( fromFile ), new File( toFile ), false );
	}

	/**
	 * ファイルのバイナリコピーを行います。
	 *
	 * copy( File,File,boolean ) を呼び出します。
	 * 第３引数の、keepTimeStamp=true で、コピー元のファイルのタイムスタンプを、
	 * コピー先にもセットします。
	 *
	 * @og.rev 5.1.6.0 (2010/05/01) 戻り値に、true/false 指定します。
	 *
	 * @param	fromFile	コピー元ファイル名
	 * @param	toFile		コピー先ファイル名
	 * @param	keepTimeStamp	タイムスタンプ維持[true/false]
	 *
	 * @return	バイナリコピーが正常に終了したかどうか[true:成功/false:失敗]
	 * @see		#copy( File,File,boolean )
	 */
	public static boolean copy( final String fromFile,final String toFile,final boolean keepTimeStamp ) {
		return copy( new File( fromFile ), new File( toFile ), keepTimeStamp );
	}

	/**
	 * ファイルのバイナリコピーを行います。
	 *
	 * copy( File,File,false ) を呼び出します。
	 *
	 * @og.rev 5.1.6.0 (2010/05/01) 戻り値に、true/false 指定します。
	 *
	 * @param	fromFile	コピー元ファイル
	 * @param	toFile		コピー先ファイル
	 *
	 * @return	バイナリコピーが正常に終了したかどうか[true:成功/false:失敗]
	 * @see		#copy( File,File,boolean )
	 */
	public static boolean copy( final File fromFile,final File toFile ) {
		return copy( fromFile, toFile, false );
	}

	/**
	 * ファイルのバイナリコピーを行います。
	 *
	 * 第３引数の、keepTimeStamp=true で、コピー元のファイルのタイムスタンプを、
	 * コピー先にもセットします。
	 * toFile が、ディレクトリの場合は、fromFile のファイル名をそのままコピーします。
	 * fromFile がディレクトリの場合は、copyDirectry( File,Fileboolean )を call します。
	 *
	 * @og.rev 5.1.6.0 (2010/05/01) 新規追加
	 * @og.rev 5.6.5.2 (2013/06/21) ByteBufferを利用した方式から、transferTo を使用する方式に変更
	 * @og.rev 5.7.1.2 (2013/12/20) copy先(toFile)のフォルダが存在しなければ、作成します。
	 * @og.rev 6.3.6.1 (2015/08/28) copy元(fromFile)がフォルダがディレクトリの場合は、#copyDirectry( File,File,boolean ) を呼ぶ。
	 * @og.rev 6.3.6.1 (2015/08/28) Exception発生時に return ではなく、StringUtil.ogErrMsg に変更。
	 * @og.rev 6.3.8.5 (2015/10/16) StringUtil.ogErrMsgPrint 使用。
	 * @og.rev 6.4.2.0 (2016/01/29) StringUtil#ogErrMsgPrint(String,Throwable) を、ThrowUtilt#ogThrowMsg(String,Throwable) に変更。
	 *
	 * @param	fromFile	コピー元ファイル
	 * @param	toFile		コピー先ファイル
	 * @param	keepTimeStamp タイムスタンプ維持[true/false]
	 *
	 * @return	バイナリコピーが正常に終了したかどうか[true:成功/false:失敗]
	 * @see		#copyDirectry( File,File,boolean )
	 */
	public static boolean copy( final File fromFile,final File toFile,final boolean keepTimeStamp ) {
		FileInputStream  inFile  = null;
		FileOutputStream outFile = null;
		FileChannel  fin  = null;
		FileChannel  fout = null;

		File tempToFile = toFile ;
		try {
			// fromFileが、ディレクトリの場合は、エラー
			if( fromFile.isDirectory() ) {
				// 6.3.6.1 (2015/08/28)
				return copyDirectry( fromFile,toFile,keepTimeStamp );
			}
			// toFileが、ディレクトリの場合は、そのパスでファイル名をfromFileから取り出す。
			if( toFile.isDirectory() ) {
				tempToFile = new File( toFile,fromFile.getName() );
			}

			// 5.7.1.2 (2013/12/20) copy先(toFile)のフォルダが存在しなければ、作成します。
			final File parent = tempToFile.getParentFile();
			if( !parent.exists() && !parent.mkdirs() ) {
				// ディレクトリを作成する
				System.err.println( parent + " の ディレクトリ作成に失敗しました。" );
				return false;
			}

			inFile  = new FileInputStream( fromFile );
			outFile = new FileOutputStream( tempToFile );

			fin  = inFile.getChannel();
			fout = outFile.getChannel();

			// 5.6.5.2 (2013/06/21) ByteBufferを利用した方式から、transferTo を使用する方式に変更

			fin.transferTo(0, fin.size(), fout );
		}
		catch( final IOException ex ) {
			// 6.3.6.1 (2015/08/28) Exception発生時に return ではなく、StringUtil.ogErrMsg に変更。
			final String errMsg = "バイナリコピーで、エラーが発生しました。" + CR
							+ "fromFile=[" + fromFile + "]" + CR
							+ "toFile  =[" + toFile   + "]" + CR ;
			// 6.3.8.5 (2015/10/16) StringUtil.ogErrMsgPrint 使用。
			System.out.println( ThrowUtil.ogThrowMsg( errMsg , ex ) );			// 6.4.2.0 (2016/01/29)
			return false;
		}
		finally {
			Closer.ioClose( inFile  ) ;
			Closer.ioClose( outFile );
			Closer.ioClose( fin  ) ;
			Closer.ioClose( fout );
		}

		if( keepTimeStamp ) {
			return tempToFile.setLastModified( fromFile.lastModified() );
		}

		return true;
	}

	/**
	 * ファイルのバイナリコピーを行います。
	 *
	 * このファイルコピーは、バイナリファイルの 改行コードを
	 * CR+LF に統一します。また、UTF-8 の BOM(0xef,0xbb,0xbf) があれば、
	 * 取り除きます。
	 *
	 * @og.rev 5.1.6.0 (2010/05/01) 新規追加
	 * @og.rev 6.3.6.1 (2015/08/28) Exception発生時に return ではなく、StringUtil.ogErrMsg に変更。
	 * @og.rev 6.3.8.5 (2015/10/16) StringUtil.ogErrMsgPrint 使用。
	 * @og.rev 6.4.2.0 (2016/01/29) StringUtil#ogErrMsgPrint(String,Throwable) を、ThrowUtilt#ogThrowMsg(String,Throwable) に変更。
	 *
	 * @param	fromFile	コピー元ファイル
	 * @param	toFile		コピー先ファイル
	 *
	 * @return	バイナリコピーが正常に終了したかどうか[true:成功/false:失敗]
	 */
	public static boolean changeCrLfcopy( final File fromFile,final File toFile ) {
		BufferedInputStream 	fromStream = null;
		BufferedOutputStream	toStream   = null;
		File tempToFile = toFile ;
		try {
			// ディレクトリの場合は、そのパスでファイル名をfromFileから取り出す。
			if( toFile.isDirectory() ) {
				tempToFile = new File( toFile,fromFile.getName() );
			}
			fromStream = new BufferedInputStream( new FileInputStream( fromFile ) );
			toStream   = new BufferedOutputStream( new FileOutputStream( tempToFile ) );

			final byte[] buf = new byte[BUFSIZE];
			int len ;
			// 4.2.3.0 (2008/05/26) changeCrLf 属性対応

			boolean bomCheck = true;	// 最初の一回だけ、ＢＯＭチェックを行う。
			byte    bt = (byte)0x00;	// バッファの最後と最初の比較時に使用
			while( (len = fromStream.read(buf,0,BUFSIZE)) != -1 ) {
				int st = 0;
				if( bomCheck && len >= 3 &&
					buf[0] == (byte)0xef &&
					buf[1] == (byte)0xbb &&
					buf[2] == (byte)0xbf  ) {
						st = 3;
				}
				else {
					// バッファの最後が CR で、先頭が LF の場合、LF をパスします。
					if( bt == B_CR && buf[0] == B_LF ) {
						st = 1 ;
					}
				}
				bomCheck = false;

				for( int i=st;i<len;i++ ) {
					bt = buf[i] ;
					if( bt == B_CR || bt == B_LF ) {
						toStream.write( (int)B_CR );		// CR
						toStream.write( (int)B_LF );		// LF
						// CR+LF の場合
						if( bt == B_CR && i+1 < len && buf[i+1] == B_LF ) {
							i++;
							bt = buf[i] ;
						}
					}
					else {
						toStream.write( (int)bt );
					}
				}
			}
			// 最後が改行コードでなければ、改行コードを追加します。
			// テキストコピーとの互換性のため
			if( bt != B_CR && bt != B_LF ) {
				toStream.write( (int)B_CR );		// CR
				toStream.write( (int)B_LF );		// LF
			}
		}
		catch( final IOException ex ) {
			// 6.3.6.1 (2015/08/28) Exception発生時に return ではなく、StringUtil.ogErrMsg に変更。
			final String errMsg = "バイナリコピー(CrLf)で、エラーが発生しました。" + CR
							+ "fromFile=[" + fromFile + "]" + CR
							+ "toFile  =[" + toFile   + "]" + CR ;
			// 6.3.8.5 (2015/10/16) StringUtil.ogErrMsgPrint 使用。
			System.out.println( ThrowUtil.ogThrowMsg( errMsg , ex ) );			// 6.4.2.0 (2016/01/29)
			return false;
		}
		finally {
			Closer.ioClose( fromStream ) ;
			Closer.ioClose( toStream ) ;
		}

		return true;
	}

	/**
	 * ファイルのバイナリコピーを行います。
	 *
	 * コピー元のファイルは、InputStream で指定します。
	 * 元は、jsp/common 以下を圧縮、jar化したため、物理ファイルの取得が
	 * できなくなったため、ServletContext#getServletContext() で、ローカルリソースを
	 * 取得するのが目的です。汎用的に、入力は、InputStream にしました。
	 * URLConnection 等で、取得する場合は、BASIC認証も考慮する必要がありますので、
	 * ご注意ください。
	 * タイムスタンプのコピーは行いません。
	 *
	 * @og.rev 6.3.6.1 (2015/08/28) InputStreamで指定されたファイルのコピー
	 * @og.rev 6.3.8.5 (2015/10/16) StringUtil.ogErrMsgPrint 使用。
	 * @og.rev 6.4.2.0 (2016/01/29) StringUtil#ogErrMsgPrint(String,Throwable) を、ThrowUtilt#ogThrowMsg(String,Throwable) に変更。
	 *
	 * @param	inStrm		コピー元のInputStream(この中でcloseします)
	 * @param	toFile		コピー先ファイル
	 *
	 * @return	バイナリコピーが正常に終了したかどうか[true:成功/false:失敗]
	 * @see		#copy( File,File )
	 */
	public static boolean copy( final InputStream inStrm,final File toFile ) {
		FileOutputStream foStrm = null;
		try {
			// copy先(toFile)のフォルダが存在しなければ、作成します。
			final File parent = toFile.getParentFile();
			if( !parent.exists() && !parent.mkdirs() ) {
				// ディレクトリを作成する
				System.err.println( parent + " の ディレクトリ作成に失敗しました。" );
				return false;
			}

			foStrm = new FileOutputStream( toFile, false );
			return copy( inStrm , foStrm );
		}
		catch( final IOException ex ) {
			// 6.3.6.1 (2015/08/28) Exception発生時に return ではなく、StringUtil.ogErrMsg に変更。
			final String errMsg = "入力ストリームのコピーでエラーが発生しました。" + CR
							+ "toFile  =[" + toFile   + "]" + CR ;
			// 6.3.8.5 (2015/10/16) StringUtil.ogErrMsgPrint 使用。
			System.out.println( ThrowUtil.ogThrowMsg( errMsg , ex ) );			// 6.4.2.0 (2016/01/29)
		}
		finally {
			Closer.ioClose( inStrm );
			Closer.ioClose( foStrm );
		}

		return false ;
	}

	/**
	 * 入出力ストリーム間でデータの転送を行います。
	 *
	 * ここでは、すでに作成されたストリームに基づき、データの入出力を行います。
	 * よって、先にフォルダ作成や、存在チェック、ファイルの削除などの必要な処理は
	 * 済まして置いてください。
	 * また、このメソッド内で、ストリームのクロース処理は行っていません。
	 *
	 * @og.rev 5.1.6.0 (2010/05/01) 新規追加
	 * @og.rev 6.3.6.1 (2015/08/28) エラー時のメッセージ情報を増やします。
	 * @og.rev 6.3.8.5 (2015/10/16) StringUtil#ogErrMsgPrint 使用。
	 * @og.rev 6.4.2.0 (2016/01/29) StringUtil#ogErrMsgPrint(String,Throwable) を、ThrowUtilt#ogThrowMsg(String,Throwable) に変更。
	 *
	 * @param	input	入力ストリーム
	 * @param	output	出力ストリーム
	 *
	 * @return	データ転送が正常に終了したかどうか[true:成功/false:失敗]
	 */
	public static boolean copy( final InputStream input,final OutputStream output ) {
		if( input == null ) {
			final String errMsg = "入力ストリームが 作成されていません。" ;
			// 6.3.8.5 (2015/10/16) StringUtil.ogErrMsgPrint 使用。
			System.out.println( ThrowUtil.ogThrowMsg( errMsg ) );			// 6.4.2.0 (2016/01/29)
			return false;
		}

		if( output == null ) {
			final String errMsg = "出力ストリームが 作成されていません。" ;
			// 6.3.8.5 (2015/10/16) StringUtil.ogErrMsgPrint 使用。
			System.out.println( ThrowUtil.ogThrowMsg( errMsg ) );			// 6.4.2.0 (2016/01/29)
			return false;
		}

		try {
			final byte[] buf = new byte[BUFSIZE];
			int len;
			while((len = input.read(buf)) != -1) {
				output.write(buf, 0, len);
			}
		}
		catch( final IOException ex ) {
			final String errMsg = "ストリームデータの入出力処理に失敗しました。";
			// 6.3.8.5 (2015/10/16) StringUtil.ogErrMsgPrint 使用。
			System.out.println( ThrowUtil.ogThrowMsg( errMsg,ex ) );			// 6.4.2.0 (2016/01/29)
			return false;
		}
	//	finally {
	//		Closer.ioClose( input );
	//		Closer.ioClose( output );
	//	}
		return true ;
	}

	/**
	 * 再帰処理でディレクトリのコピーを行います。
	 *
	 * 指定されたコピー元ディレクトリがディレクトリでなかったり存在しないときは	falseを返します。
	 *
	 * @og.rev 4.3.0.0 (2008/07/24) 追加
	 * @og.rev 5.1.6.0 (2010/05/01) 戻り値に、true/false 指定します。
	 *
	 * @param	fromDir	コピー元ディレクトリ名
	 * @param	toDir	コピー先ディレクトリ名
	 *
	 * @return	ディレクトリのコピーが正常に終了したかどうか[true:成功/false:失敗]
	 */
	public static boolean copyDirectry( final String fromDir, final String toDir ) {
		return copyDirectry( new File( fromDir ), new File( toDir ),false );
	}

	/**
	 * 再帰処理でディレクトリをコピーします。
	 *
	 * 指定されたコピー元ディレクトリがディレクトリでなかったり存在しないときは	falseを返します。
	 *
	 * @og.rev 4.3.0.0 (2008/07/24) 追加
	 * @og.rev 5.1.6.0 (2010/05/01) 内部処理を若干変更します。
	 *
	 * @param	fromDir   コピー元ディレクトリ
	 * @param	toDir     コピー先ディレクトリ
	 *
	 * @return	ディレクトリのコピーが正常に終了したかどうか[true:成功/false:失敗]
	 */
	public static boolean copyDirectry( final File fromDir, final File toDir ) {
		return copyDirectry( fromDir, toDir, false );
	}

	/**
	 * 再帰処理でディレクトリをコピーします。
	 *
	 * 指定されたコピー元ディレクトリがディレクトリでなかったり存在しないときは	falseを返します。
	 *
	 * @og.rev 4.3.0.0 (2008/07/24) 追加
	 * @og.rev 5.1.6.0 (2010/05/01) 内部処理を若干変更します。
	 * @og.rev 5.3.7.0 (2011/07/01) フォルダにアクセスできない場合は、エラーを返します。
	 *
	 * @param	fromDir	コピー元ディレクトリ
	 * @param	toDir	コピー先ディレクトリ
	 * @param	keepTimeStamp タイムスタンプ維持[true/false]
	 *
	 * @return	ディレクトリのコピーが正常に終了したかどうか[true:成功/false:失敗]
	 */
	public static boolean copyDirectry( final File fromDir, final File toDir, final boolean keepTimeStamp ) {
		// コピー元がディレクトリでない場合はfalseを返す
		// 4.3.4.4 (2009/01/01)
		if( !fromDir.exists() || !fromDir.isDirectory() ) {
			System.err.println( fromDir + " が ディレクトリでないか、存在しません。" );
			return false;
		}

		// 4.3.4.4 (2009/01/01) ディレクトリを作成する
		// 6.0.0.1 (2014/04/25) These nested if statements could be combined
		if( !toDir.exists() && !toDir.mkdirs() ) {
			System.err.println( toDir + " の ディレクトリ作成に失敗しました。" );
			return false;
		}

		// ディレクトリ内のファイルをすべて取得する
		final File[] files = fromDir.listFiles();

		// 5.3.7.0 (2011/07/01) フォルダにアクセスできない場合は、エラー
		if( files == null ) {
			System.err.println( fromDir + " はアクセスできません。" );
			return false;
		}

		// ディレクトリ内のファイルに対しコピー処理を行う
		boolean flag = true;
		for( int i=0; files.length>i; i++ ){
			if( files[i].isDirectory() ){ // ディレクトリだった場合は再帰呼び出しを行う
				flag = copyDirectry( files[i], new File( toDir, files[i].getName()),keepTimeStamp );
			}
			else{ // ファイルだった場合はファイルコピー処理を行う
				flag = copy( files[i], new File( toDir, files[i].getName()),keepTimeStamp );
			}
			if( !flag ) { return false; }
		}
		return true;
	}

	/**
	 * 指定されたファイル及びディレクトを削除します。
	 * ディレクトリの場合はサブフォルダ及びファイルも削除します。
	 * １つでもファイルの削除に失敗した場合、その時点で処理を中断しfalseを返します。
	 *
	 * @og.rev 5.3.7.0 (2011/07/01) フォルダにアクセスできない場合は、エラーを返します。
	 *
	 * @param	file 削除ファイル/ディレクトリ
	 *
	 * @return	ファイル/ディレクトリの削除に終了したかどうか[true:成功/false:失敗]
	 */
	public static boolean deleteFiles( final File file ) {
		if( file.exists() ) {
			if( file.isDirectory() ) {
				final File[] list = file.listFiles();

				// 5.3.7.0 (2011/07/01) フォルダにアクセスできない場合は、エラー
				if( list == null ) {
					System.err.println( file + " はアクセスできません。" );
					return false;
				}

				for( int i=0; i<list.length; i++ ) {
					deleteFiles( list[i] );
				}
			}
			if( !file.delete() ) { return false; }
		}
		return true;
	}

	/**
	 * 指定されたディレクトリを基点としたファイル名(パスを含む)の一覧を返します。
	 *
	 * @og.rev 4.3.6.6 (2009/05/15) 新規作成
	 * @og.rev 5.4.3.2 (2012/01/06) 引数isCopy追加
	 * @og.rev 6.3.9.0 (2015/11/06) null になっている可能性があるメソッドの戻り値のnullチェックを追加。
	 *
	 * @param dir 基点となるディレクトリ
	 * @param sort ファイル名でソートするか
	 * @param list ファイル名一覧を格納するList
	 * @param isCopy コピー中ファイルを除外するか [true:含む/false:除外]
	 */
	public static void getFileList( final File dir, final boolean sort, final List<String> list, final boolean isCopy ) {
		if( list == null ) { return; }
		if( dir.isFile() ) {
			// コピー中判定はrenameで行う
			// 6.1.0.0 (2014/12/26) refactoring : Avoid if(x != y) ..; else ..;
			if( isCopy || dir.renameTo( dir ) ) {
				list.add( dir.getAbsolutePath() );
			}
			else{
				return;
			}
		}
		else if( dir.isDirectory() ) {
			final File[] files = dir.listFiles();
			// 6.3.9.0 (2015/11/06) null になっている可能性があるメソッドの戻り値のnullチェックを追加。
			if( files != null ) {
				for( int i=0; i<files.length; i++ ) {
					getFileList( files[i], sort, list, isCopy );
				}
			}
		}
		if( sort ) {
			Collections.sort( list );
		}
	}

	/**
	 * 指定されたディレクトリを基点としたファイル名(パスを含む)の一覧を返します。
	 * 互換性のため、コピー中ファイルも含みます。
	 *
	 * @og.rev 5.4.3.2 (2012/01/06) コピー中対応のため引数４つを作成する
	 *
	 * @param dir 基点となるディレクトリ
	 * @param sort ファイル名でソートするか
	 * @param list ファイル名一覧を格納するList
	 */
	public static void getFileList( final File dir, final boolean sort, final List<String> list ) {
		getFileList( dir, sort, list, true );
	}

	/**
	 * ファイルをリネームを行います。
	 * 引数のuseBackup属性を true にすると、toFile が存在した場合、toFile の直下に "_backup" フォルダを
	 * 作成して、toFile ＋ "_" ＋ (現在時刻のLONG値) ＋ "." ＋ (toFileの拡張子) に名前変更します。
	 * useBackup属性を rename にすると、toFile が存在した場合、toFile に、"_001" からなる
	 * 連番を付与し、重複しなくなるまで、名前を変更します。
	 * useBackup属性を false(またはnull) にすると、toFile が存在した場合、toFile を削除します。
	 *
	 * 戻り値は、変更後のファイルオブジェクトです。
	 *
	 * @og.rev 5.7.1.2 (2013/12/20) 新規追加
	 * @og.rev 6.0.2.4 (2014/10/17) useBackup の機能追加
	 * @og.rev 6.2.0.0 (2015/02/27) FileInfoクラスを使用。 (#getFileSplit(File)の結果配列は廃止)
	 *
	 * @param	fromFile	名前変更する元のファイル
	 * @param	toFile		名前変更後のファイル
	 * @param	useBackup	置き換えファイルの処理方法(true:backupフォルダ/false:しない/rename:重複しない連番)
	 * @return	名前変更後のファイル
	 * @throws	RuntimeException	名称変更処理ができなかった場合。
	 */
	public static File renameTo( final File fromFile , final File toFile , final String useBackup ) {
		if( fromFile == null || toFile == null ) {
			final String errMsg = "入力ファイルが null です。from=[" + fromFile + "] , to=[" + toFile + "]" ;
			throw new OgRuntimeException( errMsg );
		}

		final File parent = toFile.getParentFile();			// 6.0.2.4 (2014/10/17) toFile のフォルダがなければ作成
		if( !parent.exists() && !parent.mkdirs() ) {
			final String errMsg = "toファイルのフォルダが作成できません。from=[" + fromFile + "] , to=[" + toFile + "]" ;
			throw new OgRuntimeException( errMsg );
		}

		// 変更先のファイルが存在した場合の処理。
		File newFile = toFile;			// useBackup = "rename" の時のみ書き換えたいので。
		if( toFile.exists() ) {
			final FileInfo info = new FileInfo( toFile );				// 6.2.0.0 (2015/02/27)
			// バックアップ作成する場合
			// 6.0.2.4 (2014/10/17) useBackup は、文字列で、true/false,(null)/rename がある。
			if( "true".equalsIgnoreCase( useBackup ) ) {
				final File backup = new File( parent , "_backup" );	// その直下に、"_backup" フォルダを作成
				if( !backup.exists() && !backup.mkdirs() ) {
					final String errMsg = "バックアップ処理でbackupフォルダの作成に失敗しました。[" + backup + "]";
					throw new OgRuntimeException( errMsg );
				}
				// バックアップファイル名は、元のファイル名(拡張子含む) ＋ "_" + 現在時刻のlong値 + "." + 元のファイルの拡張子
				final String toName = toFile.getName();
				final File toFile2  = new File( parent,toName );		// オリジナルの toFile をrename するとまずいので、同名のFileオブジェクトを作成

				final String bkupName = info.NAME + "_" + System.currentTimeMillis() + "."  + info.SUFIX ;		// 6.2.0.0 (2015/02/27)
				final File bkupFile = new File( backup,bkupName );

				if( !toFile2.renameTo( bkupFile ) ) {
					final String errMsg = "バックアップ処理でバックアップファイルをリネームできませんでした。" +CR
										 + "  [" + toFile + "] ⇒ [" + bkupFile + "]" ;
					throw new OgRuntimeException( errMsg );
				}
			}
			// 他と違い、toFile を変更する必要がある。
			else if( "rename".equalsIgnoreCase( useBackup ) ) {
				for( int i=1000; i<2000; i++ ) {			// 000 の３桁を取得したいため。
					final String no = String.valueOf( i ).substring(1);
					// 6.2.0.0 (2015/02/27) 配列ではなく、FileInfoクラスを使用
					final File toFile2 = new File( info.DIR , info.NAME + "_" + no + "." + info.SUFIX );
					if( !toFile2.exists() ) {
						newFile = toFile2;
						break;
					}
				}
			}
			// バックアップ作成しない場合は、削除します。
			else if( !toFile.delete() ) {
				final String errMsg = "既存のファイル[" + toFile + "]が削除できませんでした。";
				throw new OgRuntimeException( errMsg );
			}
		}

		if( !fromFile.renameTo( newFile ) ) {
			final String errMsg = "所定のファイルをリネームできませんでした。" + CR
								+ "  [" + fromFile + "] ⇒ [" + newFile + "]" ;
			throw new OgRuntimeException( errMsg );
		}
		return newFile;
	}

	/**
	 * ファイルを読み取って、文字列を作成します。
	 *
	 * データの読取が完全に出来なかったときには、途中までのデータを返します。
	 * 指定のエンコードが存在しない場合や、ファイルが存在しない場合は、
	 * OgRuntimeException を throw します。
	 * encode が null の場合は、UTF-8 で読み込みます。
	 *
	 * @og.rev 6.4.2.0 (2016/01/29) fukurou.util.StringUtil → fukurou.system.HybsConst に変更
	 * @og.rev 6.4.5.1 (2016/04/28) encode は初期化しているため、null はセットされません。
	 * @og.rev 6.4.5.2 (2016/05/06) fukurou.util.FileString から、fukurou.util.FileUtil に移動。
	 *
	 * @param  filename ファイル名
	 * @param  encode エンコード名
	 * @return  ファイルを読み取った文字列
	 * @throws RuntimeException 指定のエンコードが存在しなかったとき。
	 */
	public static String getValue( final String filename , final String encode ) {
		if( filename == null ) {
			final String errMsg = "ファイル名が指定されていません。" ;
			throw new OgRuntimeException( errMsg );
		}

		final String enc = encode == null ? HybsConst.UTF_8 : encode ;

		try {
			return new String( Files.readAllBytes( new File( filename ).toPath() ),enc );
		}
		catch( final IOException ex ) {
			final String errMsg = "ファイル名がオープン出来ませんでした。[" + filename + "]" ;
			throw new OgRuntimeException( errMsg,ex );
		}
	}

	/**
	 * 改行コードで分割して、Listオブジェクトを返します。
	 *
	 * encode が null の場合は、UTF-8 で読み込みます。
	 *
	 * @og.rev 6.4.5.2 (2016/05/06) fukurou.util.FileString から、fukurou.util.FileUtil に移動。
	 *
	 * @param  filename ファイル名
	 * @param  encode エンコード名
	 * @return  ファイルを読み取った文字列を分割したList
	 */
	public static List<String> getLineList( final String filename , final String encode ) {
		try {
			final String enc = encode == null ? HybsConst.UTF_8 : encode ;

			return Files.readAllLines( new File( filename ).toPath() , Charset.forName( enc ) );
		}
		catch( final IOException ex ) {
			final String errMsg = "ファイル名がオープン出来ませんでした。[" + filename + "]" ;
			throw new OgRuntimeException( errMsg,ex );
		}
	}

	/**
	 * Fileオブジェクトのサイズを返します。
	 *
	 * オブジェクトが通常のファイルの場合は、そのファイルサイズを返します。
	 * フォルダの場合は、再帰的に、ファイルサイズを加算した結果を返します。
	 *
	 * @og.rev 6.7.4.1 (2017/02/17) Fileオブジェクトのサイズを返します。
	 *
	 * @param  file Fileオブジェクト
	 * @return  ファイルまたはフォルダののサイズ
	 */
	public static long length( final File file ) {
		if( file.isDirectory() ) {
			final File[] children = file.listFiles();

			long sum = 0;
			if( children != null ) {
				for( final File child : children ) {
					sum += length( child );
				}
			}
			return sum;
		}
		return file.length();
	}

	/**
	 * PrintWriter を継承した、System.out/System.err 用のクラスを定義します。
	 *
	 * 通常の、new PrintWriter( OutputStream ) で、求めるのと、ほとんど同様の
	 * 処理を行います。
	 * ただ、close() メソッドが呼ばれても、何もしません。
	 *
	 */
	private static final class NonClosePrintWriter extends PrintWriter {
		/**
		 * コンストラクター
		 *
		 * new PrintWriter( OutputStream ) を行います。
		 *
		 * @param out OutputStreamオブジェクト
		 */
		public NonClosePrintWriter( final OutputStream out ) {
			super( out );
		}

		/**
		 * close() メソッドをオーバーライドします。
		 *
		 * 何もしません。
		 */
		@Override
		public void close() {
			// ここでは処理を行いません。
		}
	}

	/**
	 * PrintWriter を継承した、JspWriterなどの Writer 用のクラスを定義します。
	 *
	 * 例えば、JspWriterなどの JSP/Servlet等のフレームワークで使用される
	 * Writer では、flush や close 処理は、フレームワーク内で行われます。
	 * その場合、通常のファイルと同じ用に、flush や close をアプリケーション側で
	 * 行うと、内部処理的に不整合が発生したり、最悪の場合エラーになります。
	 * このクラスは、単に、通常の、new PrintWriter( Writer ) で、求めるのと、
	 * ほとんど同様の処理を行います。
	 * ただ、close() と flush() メソッドが呼ばれても、何もしません。
	 *
	 */
	private static final class NonFlushPrintWriter extends PrintWriter {
		/**
		 * コンストラクター
		 *
		 * new PrintWriter( Writer ) を行います。
		 *
		 * @param writer Writerオブジェクト
		 */
		public NonFlushPrintWriter( final Writer writer ) {
			super( writer );
		}

		/**
		 * close() メソッドをオーバーライドします。
		 *
		 * 何もしません。
		 */
		@Override
		public void close() {
			// ここでは処理を行いません。
		}

		/**
		 * flush() メソッドをオーバーライドします。
		 *
		 * 何もしません。
		 */
		@Override
		public void flush() {
			// ここでは処理を行いません。
		}
	}

	/**
	 * ファイルのエンコードを変換するコピーを行います。
	 *
	 * copy( File,File,false ) を呼び出します。
	 *
	 * @og.rev 5.1.6.0 (2010/05/01) 戻り値に、true/false 指定します。
	 *
	 * @param	file1	コピー元ファイル名
	 * @param	file2	コピー先ファイル名
	 * @param	encode1	コピー元ファイルのエンコード
	 * @param	encode2	コピー先ファイルのエンコード
	 *
	 * @see		#copy( File,File,boolean )
	 */
	public static void copy( final File file1,final File file2,final String encode1,final String encode2 ) {
	//	final File tempFile = new File( file2.getName() + "_backup" );

	//	FileUtil.copy( file2,tempFile );

		final BufferedReader reader = FileUtil.getBufferedReader( file1 ,encode1 );
		final PrintWriter    writer = FileUtil.getPrintWriter(    file2 ,encode2 );

		try {
			String line1;
			while((line1 = reader.readLine()) != null) {
				writer.println( line1 );
			}
		}
		catch( final Throwable th ) {
			th.printStackTrace();
		}
		finally {
			Closer.ioClose( reader ) ;
			Closer.ioClose( writer ) ;
		}

		// 6.9.8.0 (2018/05/28) FindBugs:例外的戻り値を無視しているメソッド
//		file2.setLastModified( file1.lastModified() );
		if( !file2.setLastModified( file1.lastModified() ) ) {
			final String errMsg = "FileUtil.copy において、タイムスタンプの更新が出来ませんでした。"	+ CR
									+ " file2= [" + file2 + "]" + CR ;
			System.err.println( errMsg );
		}
	}

	/**
	 * ファイルをコピーします。
	 *
	 * 引数に &lt;file1&gt; &lt;file2&gt; [&lt;encode1&gt; &lt;encode2&gt;] を指定します。
	 * file1 を読み込み、file2 にコピーします。コピー前に、file2 は、file2_backup にコピーします。
	 * file1 が、ディレクトリの場合は、ディレクトリごとコピーします。
	 * encode1、encode2 を指定すると、エンコード変換しながらコピーになります。
	 * この場合は、ファイル同士のコピーのみになります。
	 *
	 * @og.rev 4.0.0.0 (2007/11/28) メソッドの戻り値をチェックします。
	 * @og.rev 5.1.6.0 (2010/05/01) 引数の並び順、処理を変更します。
	 *
	 * @param	args 引数配列  file1 file2 [encode1 encode2]
	 * @throws Throwable なんらかのエラーが発生した場合。
	 */
	public static void main( final String[] args ) throws Throwable {
		if( args.length != 2 && args.length != 4 ) {
			LogWriter.log("Usage: java org.opengion.fukurou.util.FileUtil <file1> <file2> [<encode1> <encode2>]" );
			return ;
		}

		final File file1 = new File( args[0] );
		final File file2 = new File( args[1] );

		if( args.length < 3 ) {
			if( file1.isDirectory() ) {
				FileUtil.copyDirectry( file1, file2, true );
			}
			else {
				final File tempFile = new File( args[1] + "_backup" );
				FileUtil.copy( file2,tempFile );
				FileUtil.copy( file1,file2, true );
			}
		}
		else {
			final String encode1 = args[2];
			final String encode2 = args[3];

			if( file1.isDirectory() ) {
				final File[] children = file1.listFiles();

				if( children != null ) {
					for( final File child : children ) {
						copy( child , new File( file2 , child.getName() ),encode1,encode2 );
					}
				}
			}
			else {
				copy( file1,file2,encode1,encode2 );
			}
		}
	}
}
