package com.interpress_project.modernshare.client.controller.delegate;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;

import org.apache.commons.io.FileUtils;
import org.tigris.subversion.svnclientadapter.ISVNInfo;
import org.tigris.subversion.svnclientadapter.ISVNStatus;
import org.tigris.subversion.svnclientadapter.SVNClientException;
import org.tigris.subversion.svnclientadapter.SVNInfoUnversioned;
import org.tigris.subversion.svnclientadapter.SVNNodeKind;
import org.tigris.subversion.svnclientadapter.SVNRevision;
import org.tigris.subversion.svnclientadapter.SVNStatusKind;
import org.tigris.subversion.svnclientadapter.SVNUrl;

import com.interpress_project.modernshare.client.controller.delegate.exceptions.DeleteFailException;
import com.interpress_project.modernshare.client.controller.delegate.exceptions.InternalFailException;
import com.interpress_project.modernshare.client.controller.delegate.exceptions.NoNeedSyncException;
import com.interpress_project.modernshare.client.controller.delegate.exceptions.RenameFailException;
import com.interpress_project.modernshare.client.controller.model.ISVNFileItem;
import com.interpress_project.modernshare.client.controller.model.SVNFile;
import com.interpress_project.modernshare.client.controller.model.SVNFolder;
import com.interpress_project.modernshare.client.events.LogEvent;
import com.interpress_project.modernshare.client.events.LogEventType;

/**
 * Represents a singleton.
 * @stereotype Singleton factory
 */
public class LocalSVNManager extends ISVNManager {
	/**
	 * Holds singleton instance
	 */
	private static LocalSVNManager instance;

	private final SVNFolderUtil futil = SVNFolderUtil.getInstance();

	/**
	 * prevents instantiation
	 */
	private LocalSVNManager() {
	}

	/**
	 * Returns the singleton instance.
	 @return	the singleton instance
	 */
	static public LocalSVNManager getInstance() {
		if (instance == null) {
			instance = new LocalSVNManager();
		}
		return instance;
	}

	/**
	 * initialCheckoutFolder
	 * @param rootpath
	 * @param tagName
	 * @throws InternalFailException
	 */
	public void initialCheckoutFolder(File rootpath, String tagName) throws InternalFailException {
		SVNUrl u = createSVNUrl(accounturl + "/" + tagName);

		try {
			svnClient.mkdir(u, "Initial Import" + new Date().toString());
		}
		catch (SVNClientException ex) {
			throw new InternalFailException(rootpath + "に対する初期Import処理が失敗しました。", ex);
		}

		try {
			svnClient.checkout(u, rootpath, SVNRevision.HEAD, false);
			addIgnoredFilter(rootpath);
		}
		catch (SVNClientException ex) {
			throw new InternalFailException(rootpath + "に対する初期checkout処理が失敗しました。", ex);
		}
	}

	/**
	 * initialRegisterFiles
	 * @param rootpath
	 * @return
	 * @throws InternalFailException
	 */
	public File[] initialRegisterFiles(File rootpath) throws InternalFailException {
		ISVNStatus[] status = null;

		ArrayList<File> list = futil.getDirListInTarget(rootpath);
		for (int i = 0; i < list.size(); i++) {
			File file = list.get(i);
			try {
				svnClient.addDirectory(file, false);
				addIgnoredFilter(file);
			}
			catch (SVNClientException ex) {
				throw new InternalFailException("ディレクトリ" + rootpath.getAbsolutePath() + "の追加処理に失敗しました。(addDirectory)", ex);
			}
		}

		try {
			status = this.checkDirectoryStatus(rootpath, true);
		}
		catch (NoNeedSyncException ex) {
		}

		/**
		 * 上記で取得済みのstatusをそのまま利用して、ファイルだけを登録。
		 */
		for (int i = 0; i < status.length; i++) {
			ISVNStatus st = status[i];
			/**
			 * 新規追加されたファイル - SVNによるバージョン管理下にない。 
			 * ? file_or_dir
			 */
			if (SVNStatusKind.UNVERSIONED == st.getTextStatus() && (st.getFile().isDirectory() == false)) {
				this.registerFile(st.getFile());
			}
		}
		/**
		 * status情報から削除された情報を除いたFileリストを作成
		 */
		list = new ArrayList<File>();
		for (int i = 0; i < status.length; i++) {
			if (status[i].getTextStatus() == SVNStatusKind.DELETED) {
				continue;
			}
			list.add(status[i].getFile());
		}
		return (File[]) list.toArray(new File[list.size()]);
	}

	/**
	 * registerFolder
	 * 指定されたdir配下のディレクトリだけを登録する。この際svn情報フォルダの削除とフィルタの設定を行う。
	 * svnのフィルタは、既にsvn管理下にあるフォルダにのみ設定することができる。したがって、まず
	 * ディレクトリだけを先にaddした後に、フィルタを設定すること。
	 * @param dir
	 * @throws InternalFailException
	 */
	private void registerFolder(File dir) throws InternalFailException {
		if (!dir.isDirectory()) {
			return;
		}
		try {
			futil.deleteSVNFolders(dir);
		}
		catch (DeleteFailException ex) {
			throw new InternalFailException("ディレクトリ" + dir.getAbsolutePath() + "からsvn情報ファイルの削除に失敗しました。", ex);
		}

		ArrayList<File> list = futil.getDirListInTarget(dir);

		try {
			svnClient.addDirectory(dir, false);
			addIgnoredFilter(dir);
		}
		catch (SVNClientException ex) {
			throw new InternalFailException("ディレクトリ" + dir.getAbsolutePath() + "の追加に失敗しました。(addDirectory)", ex);
		}

		for (int i = 0; i < list.size(); i++) {
			File file = list.get(i);
			try {
				svnClient.addDirectory(file, false);
				addIgnoredFilter(file);
			}
			catch (SVNClientException ex) {
				throw new InternalFailException("ディレクトリ" + dir.getAbsolutePath() + "の追加に失敗しました。(addDirectory)", ex);
			}
		}
	}

	/**
	 * registerFile
	 * 単純にファイルだけをaddする。
	 * @param file
	 * @throws InternalFailException
	 */
	private void registerFile(File file) throws InternalFailException {
		if (file.isFile()) {
			try {
				svnClient.addFile(file);
			}
			catch (SVNClientException ex) {
				throw new InternalFailException("ファイル" + file.getAbsolutePath() + "の追加処理に失敗しました。(addFile)", ex);
			}
		}
	}

	/**
	 * unregisterFile
	 * @param file
	 * @throws InternalFailException
	 * @throws DeleteFailException 
	 */
	private void unregisterFile(File file) throws InternalFailException, DeleteFailException {
		if (file.isDirectory()) {
			if (svnClient.isAdminDirectory(file.getAbsolutePath())) {
				throw new DeleteFailException(file.getAbsolutePath() + "は管理ディレクトリです。削除することはできません。");
			}
		}
		try {
			svnClient.remove(new File[] { file }, true);
		}
		catch (SVNClientException ex) {
			throw new InternalFailException(file.getAbsolutePath() + "の削除処理に失敗しました。(remove)", ex);
		}
	}

	/**
	 * checkoutFiles
	 * @param rootfile
	 * @param svnurl
	 * @throws InternalFailException
	 */
	public void checkoutFiles(File rootfile, String svnurl) throws InternalFailException {
		SVNUrl u = createSVNUrl(svnurl);
		try {
			svnClient.checkout(u, rootfile, SVNRevision.HEAD, true);
		}
		catch (SVNClientException ex) {
			throw new InternalFailException(rootfile.getAbsolutePath() + "に対するチェックアウト処理に失敗しました。(" + svnurl + ")", ex);
		}
		addIgnoredFilterToDirectory(rootfile);
	}

	/**
	 * addIgnoredFilterToDirectory 
	 * @param rootfile
	 * @throws InternalFailException
	 */
	public void addIgnoredFilterToDirectory(File rootpath) throws InternalFailException {
		ArrayList<File> list = futil.getDirListInTarget(rootpath);
		addIgnoredFilter(rootpath);
		for (int i = 0; i < list.size(); i++) {
			addIgnoredFilter(list.get(i));
		}
	}

	/**
	 * getRevisionInfo
	 * @param fileName
	 * @return
	 * @throws InternalFailException
	 */
	public ArrayList<ISVNFileItem> getRevisionInfo(String fileName) throws InternalFailException {
		RemoteSVNManager rsvnmgr = RemoteSVNManager.getInstance();
		ISVNInfo info = null;
		try {
			info = svnClient.getInfo(new File(fileName));
		}
		catch (SVNClientException ex) {
			throw new InternalFailException(fileName + "に対する情報取得に失敗しました。(getInfo)", ex);
		}
		return rsvnmgr.getRevisionInfo(info.getUrlString());
	}

	/**
	 * getSVNItems
	 * @param file
	 * @return
	 * @throws InternalFailException
	 */
	public ISVNFileItem[] getSVNItems(String path) throws InternalFailException {
		ArrayList<ISVNFileItem> arrItems = new ArrayList<ISVNFileItem>();
		ISVNInfo entry = null;
		File file = new File(path);
		String abspath = file.getAbsolutePath();

		/**
		 * Check if the file exist.
		 */
		if (!file.exists()) {
			throw new InternalFailException("ローカルリソース" + abspath + "は、ローカルPC上に存在していません。");
		}

		try {
			entry = svnClient.getInfoFromWorkingCopy(file);
		}
		catch (SVNClientException ex) {
			throw new InternalFailException("ローカルリソース" + abspath + "の情報取得に失敗しました。(getInfoFromWorkingCopy)", ex);
		}
		if (entry instanceof SVNInfoUnversioned) {
			throw new InternalFailException("ローカルリソース" + abspath + "は次回同期待ち状態か同期対象外ファイルです。");
		}
		/**
		 * ユーザが不整合リソースをクリックしたかチェック
		 */
		if (entry.getNodeKind() == SVNNodeKind.DIR && file.isFile()) {
			throw new InternalFailException("ローカルリソース" + abspath + "は不整合リソースです。次回の同期処理で修正されます。");
		}
		if (entry.getNodeKind() == SVNNodeKind.FILE && file.isDirectory()) {
			throw new InternalFailException("ローカルリソース" + abspath + "は不整合リソースです。次回の同期処理で修正されます。");
		}

		ISVNFileItem item = null;
		try {
			String name = entry.getFile().getName();
			long fsize = entry.getFile().length();
			String author = entry.getLastCommitAuthor();
			Date lcdate = entry.getLastChangedDate();
			long revision = entry.getRevision().getNumber();
			long lcrevision = entry.getLastChangedRevision().getNumber();
			String url = entry.getUrlString();
			String uuid = entry.getUuid();

			if (file.isDirectory()) {
				item = new SVNFolder(name, fsize, author, lcdate, revision, lcrevision, url, uuid);
			}
			else {
				item = new SVNFile(name, fsize, author, lcdate, revision, lcrevision, url, uuid);
			}
			arrItems.add(item);
		}
		catch (Exception ex) {
			throw new InternalFailException("ローカルリソース" + abspath + "の情報取得に失敗しました。", ex);
		}
		return arrItems.toArray(new ISVNFileItem[arrItems.size()]);
	}

	/**
	 * cleanupLocalFiles - 
	 * @param path
	 * @throws InternalFailException
	 */
	public void cleanupFiles(File path) throws InternalFailException {
		sb.getLogger().debug("Enter cleanupFiles()");
		try {
			svnClient.cleanup(path);
		}
		catch (SVNClientException ex) {
			throw new InternalFailException("ローカルリソース" + path.getAbsolutePath() + "に対するCleanup処理に失敗しました。", ex);
		}
	}

	/**
	 * getStatus
	 * @param path
	 * @param bCheckRemote
	 * @return
	 * @throws InternalFailException
	 */
	private ISVNStatus[] getStatus(File path, boolean bCheckRemote) throws InternalFailException {
		sb.getLogger().debug("Enter getStatus()");

		ISVNStatus[] status = null;
		try {
			status = svnClient.getStatus(path, true, false, bCheckRemote);
		}
		catch (SVNClientException ex) {
			throw new InternalFailException("ローカルリソース" + path.getAbsolutePath() + "の変更状態取得に失敗しました。(getStatus)", ex);
		}
		return status;
	}

	/**
	 * checkDirectoryStatus 
	 * @param path
	 * @param bCheckRemote
	 * @return
	 * @throws InternalFailException
	 * @throws NoNeedSyncException
	 */
	private ISVNStatus[] checkDirectoryStatus(File path, boolean bCheckRemote) throws InternalFailException,
	    NoNeedSyncException {
		sb.getLogger().debug("Enter checkDirectoryStatus()");

		ArrayList<ISVNStatus> list = new ArrayList<ISVNStatus>();
		ISVNStatus[] status = this.getStatus(path, bCheckRemote);

		// root path については処理しないので取り除く
		// また属性NONEも無視		
		for (int i = 0; i < status.length; i++) {
			if (status[i].getFile().getAbsolutePath().equalsIgnoreCase(path.getAbsolutePath())) {
				continue;
			}
			if (status[i].getTextStatus() == SVNStatusKind.NONE) {
				continue;
			}
			list.add(status[i]);
		}
		status = (ISVNStatus[]) list.toArray(new ISVNStatus[list.size()]);
		if (status.length == 0) { // 変更されたファイルが検出されなかった場合
			throw new NoNeedSyncException();
		}

		// 検出されたが、全てフォルダだけでリモート変更NONE、属性NORMALであればそれらは無視できる。
		boolean bNoNeedUpdate = true;
		for (int i = 0; i < status.length; i++) {
			if (status[i].getNodeKind() == SVNNodeKind.DIR && status[i].getTextStatus() == SVNStatusKind.NORMAL
			    && status[i].getRepositoryTextStatus() == SVNStatusKind.NONE) {
				continue;
			}
			bNoNeedUpdate = false;
			break;
		}
		if (bNoNeedUpdate) {
			throw new NoNeedSyncException();
		}
		return status;
	}

	/**
	 * syncLocalFilesWithRemote - リモートファイルでローカルを同期する。 
	 * @param path
	 * @throws InternalFailException 
	 */
	public void syncLocalFilesWithRemote(File path) throws InternalFailException {
		ISVNStatus[] status = null;
		/*
		 *  ファイルシステム変更リソースを検出。
		 *  まずはサーバへの問い合わせを実施して最新ステータスを取得する。通常はこれで問題はないが、
		 *  OBSTRUCTED状態が発生した場合には取得エラーが発生するはず。その場合には再度、
		 *  サーバへの問い合わせ無しで取得を行う。
		 */
		try {
			status = this.checkDirectoryStatus(path, true);
		}
		catch (InternalFailException ex) {
			String msg = path + "の変更状態の取得に失敗しました。ローカルに置かれた情報を用いて再試行します。(syncLocalFilesWithRemote)";
			logEventSource.fireEvent(new LogEvent(this, msg, LogEventType.INFO));

			try {
				status = this.checkDirectoryStatus(path, false);
			}
			catch (NoNeedSyncException e) {
				return;
			}
		}
		catch (NoNeedSyncException e) {
			return;
		}

		/**
		 * ~file_or_dirの復旧処理 - リポジトリ中と作業コピー中で種類が異なっている場合には、
		 * リモートファイルでローカルファイルで修正する。
		 */
		logEventSource.fireEvent(new LogEvent(this, path + "にある不整合状態のリソースを検出しています...", LogEventType.INFO));
		for (int i = 0; i < status.length; i++) {
			ISVNStatus st = status[i];
			// ~file_or_dir
			if (SVNStatusKind.OBSTRUCTED == st.getTextStatus()) {
				File localfile = st.getFile();
				try {
					logEventSource.fireEvent(new LogEvent(this, localfile.getAbsolutePath()
					    + "は不整合状態のリソースです。データセンタに格納済みのリソースで修正を行います。", LogEventType.INFO));
					// 既にローカルにあるファイル/フォルダを.HotShot_bkup拡張子をつけて待避してあげる。
					futil.renameBkupFile(localfile);
				}
				catch (RenameFailException ex) {
					logEventSource.fireEvent(new LogEvent(this, ex.getMessage(), LogEventType.ERROR, ex));
					continue;
				}
			}
		}

		/**
		 *  OBSTRUCTEDリソース修正後のStatusを再度取得する。
		 */
		try {
			status = this.checkDirectoryStatus(path, true);
		}
		catch (NoNeedSyncException ex) {
			return;
		}

		/**
		 * ターゲットフォルダ配下に対する標準処理
		 */
		for (int i = 0; i < status.length; i++) {
			ISVNStatus st = status[i];
			/**
			 * コンフリクト状態 - ここではリモートファイルを用いてローカルファイルを修正する。
			 */
			if (SVNStatusKind.CONFLICTED == st.getTextStatus()) {
				logEventSource.fireEvent(new LogEvent(this, st.getFile().getAbsolutePath()
				    + "はデータセンタ側とコンフリクト状態になっています。データセンタに格納されたファイルでローカルファイルを更新します。", LogEventType.INFO));
				try {
					futil.renameBkupFile(st.getFile());
				}
				catch (RenameFailException ex) {
					logEventSource.fireEvent(new LogEvent(this, ex.getMessage(), LogEventType.ERROR, ex));
				}
				continue;
			}
			/**
			 * 削除されたファイル- ファイルはバージョン管理下にあるが、不完全な状態である。
			 * SVN外でOSコマンドにより削除されてしまった。
			 * ! file_or_dir
			 */
			if (SVNStatusKind.MISSING == st.getTextStatus()) {
				// svn update will recover files.
				logEventSource.fireEvent(new LogEvent(this, st.getFile().getAbsolutePath()
				    + "は削除されています。データセンタに格納されたファイルで復旧します。", LogEventType.INFO));
				continue;
			}
			/**
			 * 新規追加されたファイル - SVNによるバージョン管理下にない。 
			 * ? file_or_dir
			 */
			if (SVNStatusKind.UNVERSIONED == st.getTextStatus() || SVNStatusKind.MODIFIED == st.getTextStatus()) {
				logEventSource.fireEvent(new LogEvent(this, st.getFile().getAbsolutePath()
				    + "はローカルに追加されています。データセンタと同期するために待避します。", LogEventType.INFO));
				try {
					futil.renameBkupFile(st.getFile());
				}
				catch (RenameFailException ex) {
					logEventSource.fireEvent(new LogEvent(this, ex.getMessage(), LogEventType.ERROR, ex));
				}
				continue;
			}
		}
		/**
		 * リモートでアップデートする前にcleanupをしておくこと。
		 * これはOBSTRUCT復旧時など、ホットショットフォルダにLockが掛けれらている場合があるため
		 */
		cleanupFiles(path);

		/**
		 * リモートでローカルをアップデート
		 */
		remoteUpdate(path);
	}

	/**
	 * remoteUpdate
	 * @param localfile
	 * @throws InternalFailException 
	 */
	private void remoteUpdate(File localfile) throws InternalFailException {
		try {
			svnClient.update(localfile, SVNRevision.HEAD, true);
		}
		catch (SVNClientException ex) {
			throw new InternalFailException("ローカルリソース" + localfile.getAbsolutePath() + "の更新に失敗しました。(update)", ex);
		}
	}

	/**
	 * prepareSyncRemoteFiles - ローカルファイルでサーバを同期する。
	 * @param path
	 * @throws InternalFailException
	 */
	public File[] prepareSyncRemoteFiles(File path) throws InternalFailException {
		ISVNStatus[] status = null;

		sb.getLogger().debug("Enter prepareSyncRemoteFiles().");
		/**
		 *  ファイルシステム変更リソースを検出。
		 *  まずはサーバへの問い合わせを実施して最新ステータスを取得する。通常はこれで問題はないが、
		 *  OBSTRUCTED状態が発生した場合には取得エラーが発生するはず。その場合には再度、
		 *  サーバへの問い合わせ無しで取得を行う。
		 */
		try {
			removeNotifyListener();
			status = this.checkDirectoryStatus(path, true);
		}
		catch (InternalFailException ex) {
			String msg = path + "の変更状態の取得に失敗しました。ローカルに置かれた情報を用いて再試行します。(prepareSyncRemoteFiles)";
			logEventSource.fireEvent(new LogEvent(this, msg, LogEventType.INFO));
			try {
				status = this.checkDirectoryStatus(path, false);
			}
			catch (NoNeedSyncException e) {
				return new File[] {};
			}
		}
		catch (NoNeedSyncException e) {
			return new File[] {};
		}
		finally {
			addNotifyListener();
		}

		logEventSource.fireEvent(new LogEvent(this, path + "にある不整合状態のリソースを検出しています...", LogEventType.INFO));
		/**
		 * ~file_or_dirの復旧処理 - リポジトリ中と作業コピー中で種類が異なっている場合には
		 *  ローカルファイルでリモートファイルを修正する。
		 */
		for (int i = 0; i < status.length; i++) {
			ISVNStatus st = status[i];
			// ~file_or_dir
			if (SVNStatusKind.OBSTRUCTED == st.getTextStatus()) {
				File localfile = st.getFile();
				try {
					String msg = localfile.getAbsolutePath() + "は不整合状態のリソースです。ローカルに置かれたリソースで修正を行います。";
					logEventSource.fireEvent(new LogEvent(this, msg, LogEventType.INFO));

					if (localfile.isDirectory()) {
						RecoverFolderToFile(localfile);
					}
					else {
						RecoverFileToFolder(localfile);
					}
				}
				catch (RenameFailException ex) {
					throw new InternalFailException(ex);
				}
			}
		}

		boolean bRepeat;
		do {
			bRepeat = false;
			/*
			 *  OBSTRUCTEDリソース修正後のStatusを再取得
			 */
			try {
				status = this.checkDirectoryStatus(path, true);
			}
			catch (NoNeedSyncException ex) {
			}

			/**
			 * ターゲットフォルダ配下に対する標準処理
			 */
			for (int i = 0; i < status.length; i++) {
				ISVNStatus st = status[i];
				/**
				 * リポジトリ側でファイルが更新されている - ここではローカルファイルを用いて、
				 * リモートファイルを修正する。
				 */
				if (SVNStatusKind.MODIFIED == st.getRepositoryTextStatus()) {
					logEventSource.fireEvent(new LogEvent(this, st.getFile().getAbsolutePath()
					    + "はデータセンタ側で変更されています。ローカルファイルでデータセンタを更新します。", LogEventType.INFO));
					try {
						RecoverModifiedRemoteFileWithLocal(st);
					}
					catch (Exception ex) {
						logEventSource.fireEvent(new LogEvent(this, ex.getMessage(), LogEventType.ERROR, ex));
					}
					continue;
				}
				/**
				 * コンフリクト状態 - ここではローカルファイルを用いてリモートファイルを修正する。
				 */
				if (SVNStatusKind.CONFLICTED == st.getTextStatus()) {
					logEventSource.fireEvent(new LogEvent(this, st.getFile().getAbsolutePath()
					    + "はデータセンタ側とコンフリクト状態になっています。ローカルファイルでデータセンタを更新します。", LogEventType.INFO));
					try {
						RecoverModifiedRemoteFileWithLocal(st);
					}
					catch (Exception ex) {
						logEventSource.fireEvent(new LogEvent(this, ex.getMessage(), LogEventType.ERROR, ex));
					}
					continue;
				}

				/**
				 * 削除されたファイル- ファイルはバージョン管理下にあるが、不完全な状態である。
				 * SVN外でOSコマンドにより削除されてしまった。
				 * ! file_or_dir
				 */
				if (SVNStatusKind.MISSING == st.getTextStatus()) {
					logEventSource.fireEvent(new LogEvent(this, st.getFile().getAbsolutePath()
					    + "は削除されています。データセンタに格納されたファイルも削除します。", LogEventType.INFO));
					try {
						this.unregisterFile(st.getFile());
					}
					catch (DeleteFailException ex) {
						logEventSource.fireEvent(new LogEvent(this, ex.getMessage(), LogEventType.ERROR, ex));
					}
					catch (InternalFailException ex) {
						sb.getLogger().error(ex.getMessage());
					}
					continue;
				}

				/**
				 * 新規追加されたファイル - SVNによるバージョン管理下にない。 
				 * ? file_or_dir
				 */
				if (SVNStatusKind.UNVERSIONED == st.getTextStatus()) {
					logEventSource.fireEvent(new LogEvent(this,
					  st.getFile().getAbsolutePath() + "はローカルに追加されました。データセンタに新規登録をします。", LogEventType.INFO));
					File file = st.getFile();
					if (file.isDirectory()) {
						/**
						 * フォルダだけを追加しているため再度statusの取り直しが必要。
						 * これによってフォルダ配下のファイル更新が有効となる。
						 */
						this.registerFolder(file);
						bRepeat = true;
					}
					else {
						this.registerFile(file);
					}
					continue;
				}
			} // End of for loop.			
		}
		while (bRepeat); // End of do-while(bRpeat) loop

		/**
		 * 最終的なstatus情報を取得する。
		 */
		ArrayList<File> list = new ArrayList<File>();
		try {
			status = this.checkDirectoryStatus(path, false);
		}
		catch (NoNeedSyncException e) {
			return new File[] {};
		}
		/**
		 * status情報から削除された情報を除いたFileリストを作成
		 */
		for (int i = 0; i < status.length; i++) {
			if (status[i].getTextStatus() == SVNStatusKind.DELETED) {
				continue;
			}
			list.add(status[i].getFile());
		}
		return (File[]) list.toArray(new File[list.size()]);
	}

	/**
	 * RecoverModifiedRemoteFileWithLocal
	 * サーバ側に格納されたファイルが変更されていた場合の復旧処理。(ローカルファイルでリモートファイルを修正)
	 * @param st
	 * @throws RenameFailException
	 * @throws DeleteFailException
	 * @throws InternalFailException
	 */
	private void RecoverModifiedRemoteFileWithLocal(ISVNStatus st) throws RenameFailException, DeleteFailException,
	    InternalFailException {
		String src = st.getFile().getAbsolutePath();
		String dest = src + ".orig." + System.currentTimeMillis();
		File localfile = st.getFile();
		File destfile = new File(dest);

		if (localfile.renameTo(destfile) == false) {
			throw new RenameFailException(src + "に対するファイル名変更に失敗しました。(RecoverModifiedRemoteFileWithLocal)");
		}
		try {
			svnClient.update(localfile, SVNRevision.HEAD, true);
		}
		catch (SVNClientException ex) {
			throw new InternalFailException(ex);
		}

		try {
			//			FileUtils.deleteDirectory(localfile);
			FileUtils.forceDelete(localfile);
		}
		catch (IOException ex) {
			throw new DeleteFailException(src + "に対するファイル削除に失敗しました。(RecoverModifiedRemoteFileWithLocal)");
		}

		sb.gc(); // To gain time ...

		if (destfile.renameTo(localfile) == false) {
			throw new RenameFailException(dest + "に対するファイル名変更に失敗しました。(RecoverModifiedRemoteFileWithLocal)");
		}

		try {
			FileUtils.touch(localfile);
		}
		catch (IOException ex) {
			throw new RenameFailException(src + "に対するtouch()に失敗しました。(RecoverModifiedRemoteFileWithLocal)");
		}
	}

	/**
	 * RecoverFolderToFile - Plain file in server and folder in client.
	 * 
	 * Recovery step is below.
	 * ren a a.bkup(OS Command)
	 * svn delete a
	 * svn add a.bkup
	 * svn -m msg commit a a.bkup
	 * svn move a.bkup a
	 * svn -m msg commit a a.bkup
	 * 
	 * @param localfile
	 * @throws RenameFailException 
	 * @throws InternalFailException 
	 */
	private void RecoverFolderToFile(File localfile) throws RenameFailException, InternalFailException {
		String src = null, dist = null;
		src = localfile.getAbsolutePath();
		dist = src + ".orig." + System.currentTimeMillis();
		File bkupfile = new File(dist);
		if (localfile.renameTo(bkupfile) == false) {
			throw new RenameFailException(src + "に対するファイル名変更に失敗しました。(RecoverFolderToFile)");
		}
		try {
			svnClient.remove(new File[] { localfile }, true);
			svnClient.addFile(bkupfile);
			svnClient.commit(new File[] { localfile, bkupfile }, "Recovery step.1", true);
			svnClient.move(bkupfile, localfile, true);
			svnClient.commit(new File[] { localfile, bkupfile }, "Recovery step.2", true);
		}
		catch (SVNClientException ex) {
			throw new InternalFailException(ex);
		}
	}

	/**
	 * RecoverFileToFolder - Folder in server, plain file in client.
	 * 
	 * Recovery step is below.
	 * ren a a.bkup (OS Command)
	 * svn update -N a
	 * svn delete a
	 * svn add a.bkup
	 * svn -m msg commit a a.bkup
	 * svn move a.bkup a
	 * svn -m msg commit a a.bkup
	 * 
	 * @param localfile
	 * @throws RenameFailException 
	 * @throws InternalFailException 
	 */
	private void RecoverFileToFolder(File localfile) throws RenameFailException, InternalFailException {
		String src = null, dist = null;
		src = localfile.getAbsolutePath();
		dist = src + ".orig." + System.currentTimeMillis();
		File bkupfile = new File(dist);
		if (localfile.renameTo(bkupfile) == false) {
			throw new RenameFailException("不整合状態リソース復旧中のエラー: " + src + "に対するファイル名変更に失敗しました。(RecoverFileToFolder)");
		}
		try {
			svnClient.update(localfile, SVNRevision.HEAD, false);
			svnClient.remove(new File[] { localfile }, true);
			svnClient.addFile(bkupfile);
			svnClient.commit(new File[] { localfile, bkupfile }, "Recovery step.1", true);
			svnClient.move(bkupfile, localfile, true);
			svnClient.commit(new File[] { localfile, bkupfile }, "Recovery step.2", true);
		}
		catch (SVNClientException ex) {
			throw new InternalFailException(ex);
		}
	}
}
