/*
 * $Id: AutoFileUpdateCheck.java,v 0.0 2009/09/24 00:00:00 t-imamura Exp $
 *
 * Copyright (c) 2009 t-imamura, All rights reserved.
 */
package autofschk;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Timer;
import java.util.TimerTask;

/**
 * ファイルシステムの更新チェック. <br>
 * 監視対象にファイルを指定した場合は、そのファイルの変更を監視する。
 * 監視対象にディレクトリを指定した場合は、その配下のファイルを監視する。
 *
 * <pre>
 * File checkPath = new File(チェックするファイルもしくはディレクトリ);
 * AutoFileUpdateCheck check = new AutoFileUpdateCheck(chkPath, false, 1000L);
 * // 監視開始
 * check.start();
 * // 監視終了
 * check.stop();
 * </pre>
 */
public class AutoFileUpdateCheck {

	/** 監視用タイマースレッド */
	private static Timer timer;

	static {
		timer = new Timer(true);
	}

	/** 更新の監視対象 */
	private final File target;
	/** 監視対象を再帰的に探す */
	private final boolean recursive;
	/** 更新の監視間隔(ミリ秒) */
	private final long interval;

	/** 監視対象のパスと変更時刻 */
	private HashMap<String, Long> watch;
	/** 監視タイマータスク */
	private UpdateChecker watcher;

	/**
	 * AutoFileUpdateCheck を構築する。
	 *
	 * @param target 監視対象
	 * @param recursive 再帰的に探す
	 * @param interval 監視間隔
	 */
	public AutoFileUpdateCheck(File target, boolean recursive, long interval) {
		this.target = target;
		this.recursive = recursive;
		this.interval = interval;
		this.watch = new HashMap<String, Long>();
	}

	/**
	 * 監視を開始する。
	 */
	public synchronized void start() {
		// 監視対象の初期化
		this.watch.clear();
		for (File file : getFilse()) {
			if (!file.exists()) continue;
			watch.put(file.getPath(), file.lastModified());
		}
		// 監視タイマー始動
		this.watcher = new UpdateChecker();
		timer.schedule(this.watcher, this.interval, this.interval);
	}

	/**
	 * 監視を終了する。
	 */
	public synchronized void stop() {
		// 監視タイマー停止
		this.watcher.cancel();
		this.watcher = null;
		// 監視対象の初期化
		this.watch.clear();
	}

	/**
	 * 監視対象で変更があった(変更時刻が変わった)ファイル。
	 * @param file 該当のファイル
	 */
	protected void changedFile(File file) {
		System.out.println("Mod file. path=" + file.getPath());
	}

	/**
	 * 監視対象で追加されたファイル。
	 * @param file 該当のファイル
	 */
	protected void addedFile(File file) {
		System.out.println("Add file. path=" + file.getPath());
	}

	/**
	 * 監視対象で削除されたファイル。
	 * @param file 該当のファイル
	 */
	protected void deletedFile(File file) {
		System.out.println("Del file. path=" + file.getPath());
	}

	private synchronized void check() {
		HashSet<String> files = new HashSet<String>(this.watch.keySet());
		for (File file : getFilse()) {
			if (!file.exists()) continue;
			String path = file.getPath();
			long mtime = file.lastModified();
			if (this.watch.containsKey(path)) {
				if (mtime != this.watch.get(path)) {
					changedFile(file);
					this.watch.put(path, mtime);
				}
				files.remove(path);
			} else {
				addedFile(file);
				this.watch.put(path, mtime);
			}
		}
		for (String path : files) {
			deletedFile(new File(path));
			this.watch.remove(path);
		}
	}

	private File[] getFilse() {
		if (target.isDirectory()) {
			ArrayList<File> files = new ArrayList<File>();
			addFilse(files, target);
			return files.toArray(new File[0]);
		} else if (target.isFile()) {
			return new File[] { target };
		}
		return new File[0];
	}

	private void addFilse(ArrayList<File> files, File path) {
		for (File file : path.listFiles()) {
			if (recursive && file.isDirectory()) {
				addFilse(files, file);
			} else if (file.isFile()) {
				files.add(file);
			}
		}
		return;
	}

	private class UpdateChecker extends TimerTask {
		public void run() {
			check();
		}
	}

}
