*/\r
private static final int TRY_DISPOSE_INTERVAL = 1000;\r
\r
+ /**\r
+ * 内部クラスローダの弱参照。参照の有効性を調べることで、クラスローダの破棄が成功したかどうかを判定するために使用しています。\r
+ */\r
private WeakReference<InternalURLClassLoader> wrCL;\r
\r
+ /**\r
+ * 内部クラスローダ。クラスローダの実際の処理は、この内部クラスローダが行っています。\r
+ */\r
private InternalURLClassLoader cl;\r
\r
/**\r
* 指定されたディレクトリの形式が不正である場合。\r
*/\r
public DeployerClassLoader(File[] fileDirectories, File[] jarDirectories, ClassLoader parent) throws MalformedURLException {\r
- super(parent);\r
- this.initialize(fileDirectories, jarDirectories);\r
- }\r
-\r
- /**\r
- * <p>\r
- * {@link DeployerClassLoader}インスタンスを初期化します。\r
- * </p>\r
- * \r
- * @param fileDirectories\r
- * クラス・ファイル、リソースなどを検索するディレクトリ。複数指定することが出来ます(例えば、classes/、conf/のように)。<br>\r
- * nullの場合は無視します。配列中のnullも無視します。\r
- * @param jarDirectories\r
- * jarファイルを検索するディレクトリ。このディレクトリ以下に存在するjarファイルを読み込みます。<br>\r
- * nullの場合は無視します。配列中のnullも無視します。\r
- * @throws MalformedURLException\r
- * 指定されたディレクトリの形式が不正である場合。\r
- */\r
- public DeployerClassLoader(File[] fileDirectories, File[] jarDirectories) throws MalformedURLException {\r
super();\r
- this.initialize(fileDirectories, jarDirectories);\r
- }\r
\r
- /**\r
- * <p>\r
- * クラスローダーを初期化します。\r
- * </p>\r
- * \r
- * @param fileDirectories\r
- * クラス・ファイル、リソースなどを検索するディレクトリ。複数指定することが出来ます(例えば、classes/,\r
- * conf/のように)。<br>\r
- * nullの場合は無視します。配列中のnullも無視します。\r
- * @param jarDirectories\r
- * jarファイルを検索するディレクトリ。このディレクトリ以下に存在するjarファイルを読み込みます。<br>\r
- * nullの場合は無視します。配列中のnullも無視します。\r
- * @throws MalformedURLException\r
- * 指定されたディレクトリの形式が不正である場合。\r
- */\r
- private void initialize(File[] fileDirectories, File[] jarDirectories) throws MalformedURLException {\r
+ /*\r
+ * クラスローダの検索パスとして追加するURLを収集します。\r
+ */\r
List<URL> urlList = new ArrayList<URL>();\r
\r
+ // クラス・ファイル、リソースなどを検索するディレクトリのパスを追加します。\r
if (fileDirectories != null) {\r
for (File f : fileDirectories) {\r
if (f != null) {\r
}\r
}\r
\r
+ // jarファイルのパスを追加します。\r
if (jarDirectories != null) {\r
for (File f : jarDirectories) {\r
if (f != null) {\r
}\r
}\r
\r
- this.cl = new InternalURLClassLoader(urlList.toArray(new URL[0]));\r
+ /*\r
+ * 内部クラスローダを初期化します。\r
+ */\r
+ if (parent == null) {\r
+ this.cl = new InternalURLClassLoader(urlList.toArray(new URL[0]));\r
+ } else {\r
+ this.cl = new InternalURLClassLoader(urlList.toArray(new URL[0]), parent);\r
+ }\r
+ // 内部クラスローダの破棄を確認するため、弱参照を保持します。\r
this.wrCL = new WeakReference<InternalURLClassLoader>(this.cl);\r
}\r
\r
/**\r
* <p>\r
+ * {@link DeployerClassLoader}インスタンスを初期化します。\r
+ * </p>\r
+ * \r
+ * @param fileDirectories\r
+ * クラス・ファイル、リソースなどを検索するディレクトリ。複数指定することが出来ます(例えば、classes/、conf/のように)。<br>\r
+ * nullの場合は無視します。配列中のnullも無視します。\r
+ * @param jarDirectories\r
+ * jarファイルを検索するディレクトリ。このディレクトリ以下に存在するjarファイルを読み込みます。<br>\r
+ * nullの場合は無視します。配列中のnullも無視します。\r
+ * @throws MalformedURLException\r
+ * 指定されたディレクトリの形式が不正である場合。\r
+ */\r
+ public DeployerClassLoader(File[] fileDirectories, File[] jarDirectories) throws MalformedURLException {\r
+ this(fileDirectories, jarDirectories, null);\r
+ }\r
+\r
+ /**\r
+ * <p>\r
* 指定されたディレクトリ以下のjarファイルを検索し、そのURLをリストとして返します。\r
* </p>\r
* \r
}\r
\r
for (File f : files) {\r
- if (f.isFile() && f.getName().endsWith(".jar")) {\r
+ if (f.isFile() && f.getName().toLowerCase().endsWith(".jar")) {\r
urlList.add(f.toURL());\r
} else if (f.isDirectory()) {\r
urlList.addAll(this.getJarFileList(f));\r
* <blockquote> cl.dispose(0); </blockquote>\r
*/\r
public void dispose() {\r
- this.cl = null;\r
- while (this.wrCL.get() != null) {\r
- System.gc();\r
- }\r
+ this.dispose(0);\r
}\r
\r
/**\r
public void dispose(int timeout) {\r
this.cl = null;\r
\r
+ // タイムアウト時間が経過するまで、破棄を試みます。\r
long t = System.currentTimeMillis();\r
while (((System.currentTimeMillis() - t) < timeout) || timeout <= 0) {\r
+ // 内部クラスローダを破棄するため、強制的にガベージコレクションを試みます。\r
System.gc();\r
+\r
+ // 内部クラスローダへの弱参照がnullになっている場合、破棄されたことを表します。\r
+ // 従って、処理を抜けます。\r
if (this.wrCL.get() == null) {\r
break;\r
}\r
+\r
+ // 処理の間隔をあけるため、単位時間の間、処理を中断します。\r
try {\r
Thread.sleep(TRY_DISPOSE_INTERVAL);\r
} catch (InterruptedException e) {\r
}\r
}\r
\r
+ // 弱参照がnullではない場合、タイムアウトしたことを表します。\r
+ // 従って、例外をスローします。\r
if (this.wrCL.get() != null) {\r
throw new ClassLoaderUnloadFailException();\r
}\r
return this.cl.loadClass(name, resolve);\r
}\r
\r
+ /**\r
+ * <p>\r
+ * 内部クラスローダ。必要なメソッドのアクセス修飾子をpublicに変更するために定義します。\r
+ * </p>\r
+ * \r
+ * @author uguu\r
+ */\r
private class InternalURLClassLoader extends URLClassLoader {\r
\r
public InternalURLClassLoader(URL[] urls) {\r
package jp.sourceforge.deployer;\r
\r
import java.io.File;\r
+import java.io.FileInputStream;\r
import java.io.IOException;\r
+import java.security.MessageDigest;\r
+import java.security.NoSuchAlgorithmException;\r
import java.util.ArrayList;\r
+import java.util.Arrays;\r
import java.util.HashMap;\r
import java.util.List;\r
import java.util.Map;\r
\r
private Map<String, MonitoringFileInfo> monitoringFileMap = new HashMap<String, MonitoringFileInfo>();\r
\r
+ private boolean checkLength = true;\r
+\r
+ private boolean checkLastModified = true;\r
+\r
+ private boolean checkContent = false;\r
+\r
+ private String hashAlgorithm = "MD5";\r
+\r
+ /**\r
+ * <p>\r
+ * ファイル・サイズを監視するかどうかを取得します。初期値はtrueです。\r
+ * </p>\r
+ * \r
+ * @return ファイル・サイズを監視する場合はtrue、監視しない場合はfalse。\r
+ */\r
+ public boolean isCheckLength() {\r
+ return this.checkLength;\r
+ }\r
+\r
+ /**\r
+ * <p>\r
+ * ファイル・サイズを監視するかどうかを設定します。\r
+ * </p>\r
+ * \r
+ * @param checkLength\r
+ * ファイル・サイズを監視する場合はtrue、監視しない場合はfalse。\r
+ */\r
+ public void setCheckLength(boolean checkLength) {\r
+ this.checkLength = checkLength;\r
+ }\r
+\r
+ /**\r
+ * <p>\r
+ * 最終更新日時を監視するかどうかを取得します。初期値はtrueです。\r
+ * </p>\r
+ * \r
+ * @return 最終更新日時を監視する場合はtrue、監視しない場合はfalse。\r
+ */\r
+ public boolean isCheckLastModified() {\r
+ return this.checkLastModified;\r
+ }\r
+\r
+ /**\r
+ * <p>\r
+ * 最終更新日時を監視するかどうかを設定します。\r
+ * </p>\r
+ * \r
+ * @param checkLastModified\r
+ * 最終更新日時を監視する場合はtrue、監視しない場合はfalse。\r
+ */\r
+ public void setCheckLastModified(boolean checkLastModified) {\r
+ this.checkLastModified = checkLastModified;\r
+ }\r
+\r
+ /**\r
+ * <p>\r
+ * ファイルの内容(ハッシュ値)を監視するかどうかを取得します。初期値はfalseです。\r
+ * </p>\r
+ * \r
+ * @return ファイルの内容を監視する場合はtrue、監視しない場合はfalse。\r
+ */\r
+ public boolean isCheckContent() {\r
+ return this.checkContent;\r
+ }\r
+\r
+ /**\r
+ * <p>\r
+ * ファイルの内容(ハッシュ値)を監視するかどうかを設定します。\r
+ * </p>\r
+ * <p>\r
+ * ファイルの内容を監視すると、監視のパフォーマンスが低下する場合があります。\r
+ * </p>\r
+ * \r
+ * @param checkContent\r
+ * ファイルの内容を監視する場合はtrue、監視しない場合はfalse。\r
+ */\r
+ public void setCheckContent(boolean checkContent) {\r
+ this.checkContent = checkContent;\r
+ }\r
+\r
+ /**\r
+ * <p>\r
+ * ファイルの内容を比較するときに使用するハッシュ・アルゴリズムを取得します。初期値は"MD5"です。\r
+ * </p>\r
+ * \r
+ * @return ハッシュ・アルゴリズム。\r
+ */\r
+ public String getHashAlgorithm() {\r
+ return this.hashAlgorithm;\r
+ }\r
+\r
+ /**\r
+ * <p>\r
+ * ファイルの内容を比較するときに使用するハッシュ・アルゴリズムを設定します。\r
+ * </p>\r
+ * <p>\r
+ * ハッシュ値の計算は{@link MessageDigest}クラスを使用します。従って、このメソッドで指定するハッシュ・アルゴリズムは{@link MessageDigest}クラスがサポートするアルゴリズムである必要があります。{@link MessageDigest}クラスがサポートしないハッシュ・アルゴリズムを設定した場合、{@link #monitor()}メソッドの呼び出しで{@link FileMonitorFailException}例外がスローされる可能性があります。\r
+ * </p>\r
+ * \r
+ * @param hashAlgorithm\r
+ * ハッシュ・アルゴリズム。\r
+ */\r
+ public void setHashAlgorithm(String hashAlgorithm) {\r
+ this.hashAlgorithm = hashAlgorithm;\r
+ }\r
+\r
/**\r
* <p>\r
* 指定したディレクトリの指定したパターンに合致するファイルを監視する{@link FileMonitor}インスタンスを初期化します。\r
* ファイルの作成、更新、削除を監視し、リスナーにイベントを通知します。\r
* </p>\r
* \r
- * @throws IOException\r
- * 入出力エラーが発生した場合。\r
+ * @throws FileMonitorFailException\r
+ * ファイルの監視に失敗した場合。\r
*/\r
- public void monitor() throws IOException {\r
- this.checkCreateOrUpdate(this.baseDirectory);\r
+ public void monitor() throws FileMonitorFailException {\r
+ try {\r
+ this.checkCreateOrUpdate(this.baseDirectory);\r
+ } catch (NoSuchAlgorithmException e) {\r
+ throw new FileMonitorFailException(e);\r
+ } catch (IOException e) {\r
+ throw new FileMonitorFailException(e);\r
+ }\r
this.checkDelete();\r
this.checkClear();\r
}\r
\r
- private void checkCreateOrUpdate(File dir) throws IOException {\r
+ private void checkCreateOrUpdate(File dir) throws IOException, NoSuchAlgorithmException {\r
+ if (dir == null) {\r
+ return;\r
+ }\r
+\r
File[] files = dir.listFiles();\r
if (files == null) {\r
- throw new IOException("ディレクトリ\"" + dir.getAbsolutePath() + "\"に格納されているファイルの一覧の取得に失敗しました。");\r
+ return;\r
}\r
- for (File f : dir.listFiles()) {\r
+\r
+ for (File f : files) {\r
if (f.isFile()) {\r
this.checkFileCreateOrUpdate(f);\r
} else {\r
}\r
}\r
\r
- private void checkFileCreateOrUpdate(File file) {\r
+ private void checkFileCreateOrUpdate(File file) throws NoSuchAlgorithmException, IOException {\r
if (this.filePattern.matcher(file.getAbsolutePath()).matches()) {\r
synchronized (this.monitoringFileMap) {\r
if (!this.monitoringFileMap.containsKey(file.getAbsolutePath())) {\r
// ファイルがマップに存在しない場合、新規のモニター対象。\r
MonitoringFileInfo info = new MonitoringFileInfo();\r
info.path = file.getAbsolutePath();\r
- info.length = file.length();\r
- info.lastModified = file.lastModified();\r
- info.hashValue = ""; // TODO: ハッシュ値を算出し、格納する。\r
+ this.setMonitoringFileInfo(file, info);\r
info.checked = true;\r
this.monitoringFileMap.put(file.getAbsolutePath(), info);\r
// イベントを通知する。\r
} else {\r
// ファイルがマップに存在する場合、変更のモニター対象。\r
MonitoringFileInfo info = this.monitoringFileMap.get(file.getAbsolutePath());\r
- if (info.length != file.length() || info.lastModified != file.lastModified()) { // TODO:\r
- // ハッシュ値を比較する。\r
- info.length = file.length();\r
- info.lastModified = file.lastModified();\r
- info.hashValue = ""; // TODO: ハッシュ値を算出し、格納する。\r
- info.checked = true;\r
+ if (this.isModified(file, info)) {\r
+ this.setMonitoringFileInfo(file, info);\r
// イベントを通知する。\r
FileMonitorListener[] listeners = this.getListeners();\r
for (FileMonitorListener l : listeners) {\r
}\r
}\r
\r
+ private boolean isModified(File file, MonitoringFileInfo info) throws NoSuchAlgorithmException, IOException {\r
+ if (this.checkLength) {\r
+ if (file.length() != info.length) {\r
+ return true;\r
+ }\r
+ }\r
+ if (this.checkLastModified) {\r
+ if (file.lastModified() != info.lastModified) {\r
+ return true;\r
+ }\r
+ }\r
+ if (this.checkContent) {\r
+ if (!Arrays.equals(this.getHashData(file), info.hashData)) {\r
+ return true;\r
+ }\r
+ }\r
+ return false;\r
+ }\r
+\r
+ private void setMonitoringFileInfo(File file, MonitoringFileInfo info) throws NoSuchAlgorithmException, IOException {\r
+ if (this.checkLength) {\r
+ info.length = file.length();\r
+ }\r
+ if (this.checkLastModified) {\r
+ info.lastModified = file.lastModified();\r
+ }\r
+ if (this.checkContent) {\r
+ info.hashData = this.getHashData(file);\r
+ }\r
+ }\r
+\r
+ private byte[] getHashData(File file) throws IOException, NoSuchAlgorithmException {\r
+ MessageDigest md = MessageDigest.getInstance(this.hashAlgorithm);\r
+\r
+ FileInputStream fileIn = new FileInputStream(file);\r
+ try {\r
+ byte[] buf = new byte[1024];\r
+ int len;\r
+ while ((len = fileIn.read(buf)) != -1) {\r
+ md.update(buf, 0, len);\r
+ }\r
+ } finally {\r
+ fileIn.close();\r
+ }\r
+\r
+ return md.digest();\r
+ }\r
+\r
private void checkDelete() {\r
synchronized (this.monitoringFileMap) {\r
List<String> removeFileList = new ArrayList<String>();\r
\r
long lastModified;\r
\r
- String hashValue;\r
+ byte[] hashData;\r
\r
boolean checked = false;\r
\r