* ロガー
*/
private static final Logger logger = Logger.getLogger(PartsImageDirectoryWatchAgent.class.getName());
-
+
private final CharacterData characterData;
-
+
private final File baseDir;
-
+
/**
* リスナーとサスペンド要求マップのロックオブジェクト
*/
private final Object lockListeners = new Object();
-
+
/**
* 監視を通知されるリスナー.
*/
*/
private IdentityHashMap<PartsImageDirectoryWatchListener, Integer> suspendStateMap
= new IdentityHashMap<PartsImageDirectoryWatchListener, Integer>();
-
-
+
+
/**
* ロックオブジェクト
*/
private final Object lock = new Object();
-
-
+
+
/**
* 停止フラグ
*/
private volatile boolean stopFlag;
-
+
/**
* 監視インターバル
*/
private int dirWatchInterval;
-
+
/**
* スレッド、生成されていなければnull
*/
private Thread thread;
-
+
/**
* 監視結果1、まだ監視されていなければnull
*/
private volatile Long signature;
-
+
/**
* 監視結果2、検出されたアイテムの個数
*/
private volatile int itemCount;
-
+
/**
* 監視結果3、検出されたアイテムの最終更新日。ただし未来の日付は除外する。
*/
private volatile long maxLastModified;
-
-
+
+
public PartsImageDirectoryWatchAgentThread(CharacterData characterData) {
if (characterData == null) {
throw new IllegalArgumentException();
this.baseDir = baseDir;
this.dirWatchInterval = AppConfig.getInstance().getDirWatchInterval();
}
-
+
public CharacterData getCharcterData() {
return characterData;
}
-
+
/**
* 監視を開始する.<br>
* 一定時間待機後、フォルダを監視し、前回監視結果と比較して変更があれば通知を行う.<br>
thread.start();
}
}
-
+
/**
* 監視を停止する.<br>
- *
+ *
* @return 停止した場合はtrue、すでに停止していたか開始されていない場合はfalse
*/
private boolean stop() {
}
stopped = true;
}
-
+
// スレッドは停止されていると見なす.
thread = null;
}
return stopped;
}
-
-
+
+
/**
* スレッドの停止フラグがたてられるまで、一定時間待機と監視とを永久に繰り返す.<br>
* ただし、スレッド自身はデーモンとして動作させているので他の非デーモンスレッドが存在しなくなれば停止する.<br>
// 初回スキャンは無視するためリセット状態とする.
this.signature = null;
-
+
// スキャンループ
while (!stopFlag) {
try {
}
logger.log(Level.FINE, "watch-dir thead stopped. " + this);
}
-
+
/**
* 監視を行う.<br>
* 停止フラグが設定されるか、割り込みされた場合は処理を中断してInterruptedException例外を返して終了する.<br>
- *
+ *
* @param notifier
* 通知するためのオブジェクト
* @throws InterruptedException
int itemCount = 0;
long maxLastModified = 0;
long now = System.currentTimeMillis() + dirWatchInterval;
-
+
CRC32 crc = new CRC32();
for (PartsCategory partsCategory : characterData.getPartsCategories()) {
for (Layer layer : partsCategory.getLayers()) {
Collections.sort(files);
}
for (String file : files) {
+ // 同一動作環境上でのファイル名の比較に使うためなので
+ // 文字コードは現在のデフォルトで良い。
crc.update(file.getBytes());
}
}
this.signature = Long.valueOf(signature);
this.maxLastModified = maxLastModified;
this.itemCount = itemCount;
-
+
}
-
+
/**
* イベントリスナを登録する
- *
+ *
* @param l
* リスナ
*/
changeSuspendState();
}
}
-
+
/**
* イベントリスナを登録解除する
- *
+ *
* @param l
* リスナ
*/
changeSuspendState();
}
}
-
+
public void suspend(PartsImageDirectoryWatchListener l) {
if (l != null) {
synchronized (lockListeners) {
changeSuspendState();
}
}
-
+
public void resume(PartsImageDirectoryWatchListener l) {
if (l != null) {
synchronized (lockListeners) {
changeSuspendState();
}
}
-
+
protected void changeSuspendState() {
boolean active;
synchronized (lockListeners) {
stop();
}
}
-
+
/**
* イベントを通知する.
*/
import java.io.File;
import java.net.URI;
+import java.nio.charset.Charset;
+import java.util.Arrays;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
* @return 保存先
*/
public UserData getMangledNamedUserData(URI docBase, String name) {
- String mangledName = getMangledName(docBase);
- return getUserData(mangledName + "-" + name);
+ // Mangled名はファイル名を既定でUTF-8変換しハッシュ化したものを使用する
+ Charset fileNameEncoding = getMangledEncoding();
+ String mangledName = getMangledName(docBase, fileNameEncoding);
+ FileUserData userData = (FileUserData) getUserData(mangledName + "-" + name);
+
+ if (!userData.exists()) {
+ // 指定されたMangled名が、まだ実在しない場合
+ // システムデフォルトの文字コードでMangledされた旧名があれば
+ // 旧名のファイル名をUTF-8によるMangledされたものにリネームする。
+ // (ver0.999まではmangledの文字コードを明示していなかったため、
+ // java5以前はシステム固有、java6以降ではfile.encodingのシステムプロパティで変動していた)
+ for (Charset defaultEncoding : getSystemEncodings()) {
+ if (!fileNameEncoding.equals(defaultEncoding)) {
+ String oldMangledName = getMangledName(docBase, defaultEncoding);
+ FileUserData oldUserData = (FileUserData) getUserData(oldMangledName + "-" + name);
+ if (oldUserData.exists()) {
+ logger.log(Level.INFO, "rename mangled: (" + defaultEncoding + ")" + oldUserData.getFile()
+ + " -> (" + fileNameEncoding + ")" + userData.getFile());
+ oldUserData.getFile().renameTo(userData.getFile());
+ break;
+ }
+ }
+ }
+ }
+ return userData;
}
/**
*
* @param docBase
* URI、null可
+ * @param encoding
+ * docBase名をハッシュ化するための文字コード
* @return 名前ベースのUUID
*/
- private String getMangledName(URI docBase) {
+ private String getMangledName(URI docBase, Charset encoding) {
String docBaseStr;
if (docBase == null) {
docBaseStr = "";
} else {
docBaseStr = docBase.toString();
}
- String mangledName = UUID.nameUUIDFromBytes(docBaseStr.getBytes()).toString();
+ String mangledName = UUID.nameUUIDFromBytes(docBaseStr.getBytes(encoding)).toString();
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "mangledName " + docBase + "=" + mangledName);
return mangledName;
}
+
+ /**
+ * Mangled名生成に使う文字コード
+ */
+ private static Charset mangledEncoding;
+
+ /**
+ * システムのデフォルトの文字コードの候補。
+ */
+ private static Charset[] systemEncodings;
+
+ /**
+ * Mandled名生成に使う文字コードを指定するシステムプロパティ名
+ */
+ public static final String KEY_MANGLED_ENCODING = "charactermanaj.mangled.encoding";
+
+ /**
+ *
+ * @return
+ */
+ private static Charset getMangledEncoding() {
+ if (mangledEncoding == null) {
+ String encName = System.getProperty(KEY_MANGLED_ENCODING, "UTF-8");
+ Charset fileNameEncoding = null;
+ if (encName != null && encName.length() > 0) {
+ try {
+ fileNameEncoding = Charset.forName(encName);
+ } catch (Exception ex) {
+ logger.log(Level.SEVERE, "unsupported charset: " + encName, ex);
+ }
+ }
+ if (fileNameEncoding == null) {
+ // だめならシステム標準にする
+ fileNameEncoding = Charset.defaultCharset();
+ }
+ mangledEncoding = fileNameEncoding;
+ }
+ return mangledEncoding;
+ }
+
+ /**
+ * システムのデフォルトの文字コードの候補リストを取得する。
+ * コンテンツの文字コード(file.encoding)とファイル名の文字コード(sun.jnu.encoding)の両方を取得する。
+ * ただし、どちらも同じコードである場合は1つにまとめられる。
+ * これらのシステムプロパティのデフォルト値が、どのように決定されるかは以下参照。
+ * https://stackoverflow.com/questions/1006276/what-is-the-default-encoding-of-the-jvm
+ * http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/native/java/lang/System.c#l169
+ * http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/solaris/native/java/lang/java_props_md.c#l427
+ * @return システムのデフォルトの文字コードの候補リスト
+ */
+ private static Charset[] getSystemEncodings() {
+ if (systemEncodings == null) {
+ String[] keys = { "file.encoding", "sun.jnu.encoding" };
+ Charset[] encodings = new Charset[keys.length];
+ int cnt = 0;
+ for (String key : keys) {
+ String encodingName = System.getProperty(key);
+ if (encodingName != null && encodingName.length() > 0) {
+ Charset encoding;
+ try {
+ encoding = Charset.forName(encodingName);
+ } catch (Exception ex) {
+ // 基本的にはありえない
+ logger.log(Level.SEVERE, "invalid charset:" + encodingName, ex);
+ continue;
+ }
+
+ boolean found = false;
+ for (int idx = 0; idx < cnt; idx++) {
+ if (encodings[idx].equals(encoding)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ encodings[cnt++] = encoding;
+ }
+ }
+ }
+ systemEncodings = Arrays.copyOf(encodings, cnt);
+ }
+ return systemEncodings;
+ }
}