OSDN Git Service

・キャラクターディレクトリの選択可能化に伴う、キャッシュのパージ処理の変更
authorseraphy <seraphy@5b6e9025-a2e8-4882-b233-f889982098c5>
Sun, 13 Mar 2011 16:41:23 +0000 (16:41 +0000)
committerseraphy <seraphy@5b6e9025-a2e8-4882-b233-f889982098c5>
Sun, 13 Mar 2011 16:41:23 +0000 (16:41 +0000)
・不要コード除去
・Linuxの場合のボタン順序をMacOSXと同じにする。
・ログ出力内容の調整
・最後に使用したキャラクターデータディレクトリごとのキャラクターセットの選択の保存と復帰に対応
・キャラクターディレクトリの選択のデフォルト設定解除対応
・コメント訂正
・リソース修正

git-svn-id: https://svn.sourceforge.jp/svnroot/charactermanaj/trunk@17 5b6e9025-a2e8-4882-b233-f889982098c5

24 files changed:
CharacterManaJ.jar
charactermanaj.exe
resources/languages/appconfigdialog.xml
resources/languages/appconfigdialog_ja.xml
resources/languages/selectCharatersDirDialog.xml [new file with mode: 0644]
resources/languages/selectCharatersDirDialog_ja.xml [new file with mode: 0644]
src/charactermanaj/Main.java
src/charactermanaj/graphics/AsyncImageBuilder.java
src/charactermanaj/model/io/AbstractCharacterDataArchiveFile.java
src/charactermanaj/model/io/PartsImageDirectoryWatchAgentThread.java
src/charactermanaj/model/io/RecentDataPersistent.java
src/charactermanaj/model/util/StartupSupport.java
src/charactermanaj/ui/AppConfigDialog.java
src/charactermanaj/ui/ExportWizardDialog.java
src/charactermanaj/ui/ImportWizardDialog.java
src/charactermanaj/ui/PartsManageDialog.java
src/charactermanaj/ui/ProfileEditDialog.java
src/charactermanaj/ui/ProfileListManager.java
src/charactermanaj/ui/ProfileSelectorDialog.java
src/charactermanaj/ui/RecentCharactersDir.java [new file with mode: 0644]
src/charactermanaj/ui/SelectCharatersDirDialog.java
src/charactermanaj/util/ConfigurationDirUtilities.java
src/charactermanaj/util/FileMappedProperties.java [new file with mode: 0644]
src/charactermanaj/util/UserDataFactory.java

index 5f880e1..4da1019 100644 (file)
Binary files a/CharacterManaJ.jar and b/CharacterManaJ.jar differ
index 0eddcc7..c246882 100644 (file)
Binary files a/charactermanaj.exe and b/charactermanaj.exe differ
index e71e7ac..2ffcf93 100644 (file)
@@ -6,6 +6,7 @@
 <entry key="title">Application Configurations</entry>\r
 <entry key="btn.apply">Apply</entry>\r
 <entry key="btn.cancel">Cancel</entry>\r
+<entry key="chk.askForCharactersDir">Ask the data directory during startup.</entry>\r
 <entry key="column.key">Key</entry>\r
 <entry key="column.value">Value</entry>\r
 <entry key="column.key.width">200</entry>\r
index 3551bce..bb0a49d 100644 (file)
@@ -5,6 +5,7 @@
 <entry key="title">アプリケーションの設定</entry>\r
 <entry key="btn.apply">更新</entry>\r
 <entry key="btn.cancel">キャンセル</entry>\r
+<entry key="chk.askForCharactersDir">起動時にデータディレクトリを選択する.</entry>\r
 <entry key="column.key">プロパティ名</entry>\r
 <entry key="column.value">設定値</entry>\r
 <entry key="column.key.width">200</entry>\r
diff --git a/resources/languages/selectCharatersDirDialog.xml b/resources/languages/selectCharatersDirDialog.xml
new file mode 100644 (file)
index 0000000..9770bc6
--- /dev/null
@@ -0,0 +1,13 @@
+<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">\r
+<properties version="1.0">\r
+<entry key="width">550</entry>\r
+<entry key="title">CharacterManaJ</entry>\r
+<entry key="caption">Select a workspace</entry>\r
+<entry key="lbl.dir">Workspace:</entry>\r
+<entry key="btn.ok">OK</entry>\r
+<entry key="btn.cancel">Cancel</entry>\r
+<entry key="btn.chooseDir">Browse</entry>\r
+<entry key="btn.clearRecentList">Clear recent list</entry>\r
+<entry key="chk.doNotAskAgein">Use this as the default and do not ask again.</entry>\r
+</properties>\r
+\r
diff --git a/resources/languages/selectCharatersDirDialog_ja.xml b/resources/languages/selectCharatersDirDialog_ja.xml
new file mode 100644 (file)
index 0000000..b614c7a
--- /dev/null
@@ -0,0 +1,11 @@
+<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">\r
+<properties version="1.0">\r
+<entry key="title">キャラクターなんとかJ</entry>\r
+<entry key="caption">キャラクターデータを格納するディレクトリを選択してください。</entry>\r
+<entry key="lbl.dir">ディレクトリ:</entry>\r
+<entry key="btn.ok">選択</entry>\r
+<entry key="btn.cancel">キャンセル</entry>\r
+<entry key="btn.chooseDir">参照</entry>\r
+<entry key="btn.clearRecentList">履歴のクリア</entry>\r
+<entry key="chk.doNotAskAgein">次回から、このディレクトリを使用する.</entry>\r
+</properties>\r
index ae54a53..46988f9 100644 (file)
@@ -38,6 +38,12 @@ public final class Main implements Runnable {
        private static final boolean isMacOSX;\r
        \r
        /**\r
+        * Mac OS XもしくはLinuxであるか?\r
+        */\r
+       private static final boolean isLinuxOrMacOSX;\r
+\r
+       \r
+       /**\r
         * クラスイニシャライザ\r
         */\r
        static {\r
@@ -47,6 +53,7 @@ public final class Main implements Runnable {
                // なによりも、まず、これを判定しないとダメ.(順序が重要)\r
                String lcOS = System.getProperty("os.name").toLowerCase();\r
                isMacOSX = lcOS.startsWith("mac os x");\r
+               isLinuxOrMacOSX = isMacOSX || lcOS.indexOf("linux") >= 0;\r
 \r
                // ロガーの準備\r
                try {\r
@@ -179,5 +186,12 @@ public final class Main implements Runnable {
        public static boolean isMacOSX() {\r
                return isMacOSX;\r
        }\r
-       \r
+\r
+       /**\r
+        * Mac OS X、もしくはlinuxで動作しているか?\r
+        * @return Mac OS X、もしくはlinuxで動作していればtrue\r
+        */\r
+       public static boolean isLinuxOrMacOSX() {\r
+               return isLinuxOrMacOSX;\r
+       }\r
 }\r
index e96cd40..18112fd 100644 (file)
@@ -98,7 +98,7 @@ public class AsyncImageBuilder extends ImageBuilder implements Runnable {
                                AsyncImageBuilder.super.requestJob(job);\r
                                \r
                        } catch (InterruptedException ex) {\r
-                               logger.log(Level.FINE, "AsyncImageBuilder thead interrupted.", ex);\r
+                               logger.log(Level.FINE, "AsyncImageBuilder thead interrupted.");\r
                                // 割り込みされた場合、単にループを再開する.\r
                                \r
                        } catch (Exception ex) {\r
index 96f4fc5..f30c6bb 100644 (file)
@@ -426,7 +426,7 @@ public abstract class AbstractCharacterDataArchiveFile implements CharacterDataA
 \r
                CharacterDataPersistent persist = CharacterDataPersistent.getInstance();\r
 \r
-               // character.xmlとして妥当な文書であるか検査する.\r
+               // favorites.xmlとして妥当な文書であるか検査する.\r
                DocInfo docInfo;\r
                InputStream is = favoritesXml.openStream();\r
                try {\r
@@ -440,10 +440,14 @@ public abstract class AbstractCharacterDataArchiveFile implements CharacterDataA
                        return;\r
                }\r
                \r
-               // character.xmlを読み込む\r
+               // favorites.xmlを読み込む\r
                is = favoritesXml.openStream();\r
                try {\r
                        persist.loadPartsSet(characterData, is, docInfo);\r
+\r
+               } catch (Exception ex) {\r
+                       logger.log(Level.INFO, "favorites.xml load failed.", ex);\r
+               \r
                } finally {\r
                        is.close();\r
                }\r
index ef88aa2..1acfa9b 100644 (file)
@@ -177,7 +177,7 @@ public class PartsImageDirectoryWatchAgentThread implements Runnable {
                                });\r
 \r
                        } catch (InterruptedException ex) {\r
-                               logger.log(Level.FINE, "watch-dir thead interrupted.", ex);\r
+                               logger.log(Level.FINE, "watch-dir thead interrupted.");\r
                                // 何もしない\r
                        } catch (Exception ex) {\r
                                logger.log(Level.SEVERE, "PartsImageDirectoryWatchAgent failed.", ex);\r
index a83d70d..bc5b4c4 100644 (file)
@@ -1,12 +1,16 @@
 package charactermanaj.model.io;\r
 \r
+import java.io.File;\r
 import java.io.IOException;\r
+import java.util.HashMap;\r
+import java.util.Map;\r
 import java.util.logging.Level;\r
 import java.util.logging.Logger;\r
 \r
 import charactermanaj.model.AppConfig;\r
 import charactermanaj.model.CharacterData;\r
 import charactermanaj.model.RecentData;\r
+import charactermanaj.util.DirectoryConfig;\r
 import charactermanaj.util.UserData;\r
 import charactermanaj.util.UserDataFactory;\r
 \r
@@ -67,7 +71,36 @@ public final class RecentDataPersistent {
                recentData.setDocBase(characterData.getDocBase());\r
                \r
                UserData recentCharacterStore = getRecentCharacterStore();\r
-               recentCharacterStore.save(recentData);\r
+\r
+               // 他のキャラクターディレクトリ上のデータを取得しマージする.\r
+               Map<File, RecentData> recentDataMap = new HashMap<File, RecentData>();\r
+               try {\r
+                       if (recentCharacterStore.exists()) {\r
+                               Object rawRecentData = recentCharacterStore.load();\r
+                               if (rawRecentData instanceof Map) {\r
+                                       @SuppressWarnings("unchecked")\r
+                                       Map<File, RecentData> prevRecentDataMap = (Map<File, RecentData>) rawRecentData;\r
+                                       for (Map.Entry<File, RecentData> entry : prevRecentDataMap.entrySet()) {\r
+                                               File dir = entry.getKey();\r
+                                               if (dir.exists() && dir.isDirectory()) {\r
+                                                       // 現在も有効なものだけを保存する.\r
+                                                       // (すでに存在しなくなっているものは除外する.)\r
+                                                       RecentData prevRecentData = entry.getValue();\r
+                                                       recentDataMap.put(dir, prevRecentData);\r
+                                               }\r
+                                       }\r
+                               }\r
+                       }\r
+\r
+               } catch (Exception ex) {\r
+                       logger.log(Level.WARNING, "old recentDataFile load failed.", ex);\r
+                       // 古い情報が取得できない場合でも最新のものだけは保存できるようにしておく。\r
+               }\r
+\r
+               // 保存する.\r
+               File currentCharactersDir = DirectoryConfig.getInstance().getCharactersDir();\r
+               recentDataMap.put(currentCharactersDir, recentData);\r
+               recentCharacterStore.save(recentDataMap);\r
        }\r
        \r
        /**\r
@@ -83,16 +116,39 @@ public final class RecentDataPersistent {
 \r
                RecentData recentData;\r
                try {\r
-                       recentData = (RecentData) recentCharacterStore.load();\r
+                       Object rawRecentData = recentCharacterStore.load();\r
+                       if (rawRecentData instanceof RecentData) {\r
+                               // 旧形式 (単一)\r
+                               recentData = (RecentData) rawRecentData;\r
+\r
+                       } else if (rawRecentData instanceof Map) {\r
+                               // 新形式 (複数のキャラクターディレクトリに対応)\r
+                               @SuppressWarnings("unchecked")\r
+                               Map<File, RecentData> recentDataMap = (Map<File, RecentData>) rawRecentData;\r
+                               File currentCharactersDir = DirectoryConfig.getInstance().getCharactersDir();\r
+                               recentData = recentDataMap.get(currentCharactersDir);\r
+\r
+                       } else {\r
+                               // 不明な形式\r
+                               logger.log(Level.SEVERE,\r
+                                               "invalid file format. " + recentCharacterStore\r
+                                                               + "/class=" + rawRecentData.getClass());\r
+                               recentData = null;\r
+                       }\r
 \r
                } catch (Exception ex) {\r
                        // RecentData情報の復元に失敗した場合は最後に使用したデータが存在しないものとみなす.\r
                        logger.log(Level.WARNING, "recent data loading failed. " + recentCharacterStore, ex);\r
-                       return null;\r
+                       recentData = null;\r
+               }\r
+\r
+               if (recentData != null) {\r
+                       CharacterDataPersistent persist = CharacterDataPersistent.getInstance();\r
+                       return persist.loadProfile(recentData.getDocBase());\r
                }\r
 \r
-               CharacterDataPersistent persist = CharacterDataPersistent.getInstance();\r
-               return persist.loadProfile(recentData.getDocBase());\r
+               // 履歴がない場合、もしくは読み取れなかった場合はnullを返す.\r
+               return null;\r
        }\r
        \r
        /**\r
index dd8ad33..01a9034 100644 (file)
@@ -3,6 +3,8 @@ package charactermanaj.model.util;
 import java.io.File;\r
 import java.net.URI;\r
 import java.net.URL;\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
 import java.util.HashMap;\r
 import java.util.Map;\r
 import java.util.UUID;\r
@@ -14,6 +16,7 @@ import charactermanaj.model.io.CharacterDataPersistent;
 import charactermanaj.util.DirectoryConfig;\r
 import charactermanaj.util.UserDataFactory;\r
 \r
+\r
 /**\r
  * 開始前の事前準備するためのサポートクラス \r
  * @author seraphy\r
@@ -135,15 +138,26 @@ abstract class StartupSupportForDocBasedData extends StartupSupport {
        protected enum DocBaseSignatureStoratage {\r
                \r
                /**\r
-                * 新形式のcharacter.xmlのUUIDを取得する.\r
+                * 新形式のcharacter.xmlのUUIDを取得(もしくは生成)する.\r
                 * charatcer.xmlファイルのURIを文字列にしたもののタイプ3-UUID表現.<br>\r
                 */\r
                NEW_FORMAT() {\r
                        @Override\r
-                       public String getDocBaseSignature(File characterXmlFile) {\r
-                               URI docBase = characterXmlFile.toURI();\r
+                       public Map<File, String> getDocBaseSignature(Collection<File> characterXmlFiles) {\r
+                               HashMap<URI, File> uris = new HashMap<URI, File>();\r
+                               for (File characterXmlFile : characterXmlFiles) {\r
+                                       uris.put(characterXmlFile.toURI(), characterXmlFile);\r
+                               }\r
                                UserDataFactory userDataFactory = UserDataFactory.getInstance();\r
-                               return userDataFactory.getMangledNamedPrefix(docBase);\r
+                               HashMap<File, String> results = new HashMap<File, String>();\r
+                               File storeDir = userDataFactory.getSpecialDataDir("*.ser");\r
+                               for (Map.Entry<URI, String> entry : userDataFactory\r
+                                               .getMangledNameMap(uris.keySet(), storeDir, true).entrySet()) {\r
+                                       File characterXmlFile = uris.get(entry.getKey());\r
+                                       String mangledName = entry.getValue();\r
+                                       results.put(characterXmlFile, mangledName);\r
+                               }\r
+                               return results;\r
                        }\r
                },\r
                \r
@@ -153,18 +167,24 @@ abstract class StartupSupportForDocBasedData extends StartupSupport {
                 */\r
                OLD_FORMAT() {\r
                        @Override\r
-                       public String getDocBaseSignature(File characterXmlFile) {\r
-                               try {\r
-                                       @SuppressWarnings("deprecation")\r
-                                       URL url = characterXmlFile.toURL();\r
-                                       return UUID.nameUUIDFromBytes(url.toString().getBytes()).toString();\r
+                       public Map<File, String> getDocBaseSignature(Collection<File> characterXmlFiles) {\r
+                               HashMap<File, String> results = new HashMap<File, String>();\r
+                               for (File characterXmlFile : characterXmlFiles) {\r
+                                       String mangledName;\r
+                                       try {\r
+                                               @SuppressWarnings("deprecation")\r
+                                               URL url = characterXmlFile.toURL();\r
+                                               mangledName = UUID.nameUUIDFromBytes(url.toString().getBytes()).toString();\r
 \r
-                               } catch (Exception ex) {\r
-                                       logger.log(Level.WARNING,\r
-                                                       "character.xmlのファイル位置をUUID化できません。:"\r
-                                                                       + characterXmlFile, ex);\r
-                                       return null;\r
+                                       } catch (Exception ex) {\r
+                                               logger.log(Level.WARNING,\r
+                                                               "character.xmlのファイル位置をUUID化できません。:"\r
+                                                                               + characterXmlFile, ex);\r
+                                               mangledName = null;\r
+                                       }\r
+                                       results.put(characterXmlFile, mangledName);\r
                                }\r
+                               return results;\r
                        }\r
                },\r
                ;\r
@@ -177,29 +197,33 @@ abstract class StartupSupportForDocBasedData extends StartupSupport {
                \r
                /**\r
                 * character.xmlからuuid表現のプレフィックスを算定する.\r
-                * @param characterXmlFile character.xmlのファイル\r
-                * @return UUID\r
+                * @param characterXmlFile character.xmlのファイルのコレクション\r
+                * @return character.xmlファイルと、それに対応するUUIDのマップ、(UUIDは該当がなければnull)\r
                 */\r
-               public abstract String getDocBaseSignature(File characterXmlFile);\r
+               public abstract Map<File, String> getDocBaseSignature(Collection<File> characterXmlFiles);\r
        }\r
        \r
        /**\r
-        * すべてのユーザおよびシステムのキャラクターデータのDocBaseをもととしたハッシュ値(Prefix)の文字列をキーとし、\r
-        * キャラクターディレクトリを値とするマップを返す.<br>\r
+        * すべてのユーザおよびシステムのキャラクターデータのDocBaseをもととした、\r
+        * キャッシュディレクトリ上のハッシュ値(Prefix)の文字列をキーとし、\r
+        * そのキャラクターディレクトリを値とするマップを返す.<br>\r
+        * (新タイプの場合は実在するcharacter.xmlに対するmangledNameが生成され登録される.)<br>\r
+        * @param storatage ハッシュ値を生成する戦略(旧タイプ・新タイプのUUIDの区別のため)\r
         * @return DocBaseをもととしたハッシュ値の文字列表記をキー、キャラクターディレクトリを値とするマップ\r
         */\r
-       protected Map<String, File> getDocBaseMap(DocBaseSignatureStoratage storatage) {\r
+       protected Map<String, File> getDocBaseMapInCaches(DocBaseSignatureStoratage storatage) {\r
                if (storatage == null) {\r
                        throw new IllegalArgumentException();\r
                }\r
 \r
+               // キャラクターデータフォルダ\r
                DirectoryConfig dirConfig = DirectoryConfig.getInstance();\r
                File[] charactersDirs = {\r
-                               dirConfig.getCharactersDir()\r
+                               dirConfig.getCharactersDir() // 現在のキャラクターデータフォルダ\r
                };\r
                \r
-               // キャラクターデータディレクトリを走査しdocBaseの識別子の一覧を取得する\r
-               Map<String, File> docBaseSignatures = new HashMap<String, File>();\r
+               // キャラクターデータディレクトリを走査し、character.xmlファイルを収集する.\r
+               ArrayList<File> characterXmlFiles = new ArrayList<File>();\r
                for (File charactersDir : charactersDirs) {\r
                        if (charactersDir == null || !charactersDir.exists()\r
                                        || !charactersDir.isDirectory()) {\r
@@ -213,12 +237,18 @@ abstract class StartupSupportForDocBasedData extends StartupSupport {
                                if ( !characterXml.exists()) {\r
                                        continue;\r
                                }\r
-                               String docBaseSig = storatage.getDocBaseSignature(characterXml);\r
-                               if (docBaseSig != null) {\r
-                                       docBaseSignatures.put(docBaseSig, characterDir);\r
-                               }\r
+                               characterXmlFiles.add(characterXml);\r
                        }\r
                }\r
+\r
+               // character.xmlファイルに対するハッシュ化文字列を取得する.\r
+               Map<File, String> docBaseSigMap = storatage.getDocBaseSignature(characterXmlFiles);\r
+               \r
+               // ハッシュ化文字列をキーとし、そのcharacter.xmlファイルを値とするマップに変換する.\r
+               HashMap<String, File> docBaseSignatures = new HashMap<String, File>();\r
+               for (Map.Entry<File, String> entry : docBaseSigMap.entrySet()) {\r
+                       docBaseSignatures.put(entry.getValue(), entry.getKey());\r
+               }\r
                return docBaseSignatures;\r
        }\r
        \r
@@ -276,7 +306,7 @@ class UpgradeFavoritesXml extends StartupSupportForDocBasedData {
                File appData = userDataFactory.getSpecialDataDir(null);\r
 \r
                // キャラクターデータディレクトリを走査しdocBaseの識別子の一覧を取得する\r
-               Map<String, File> docBaseSignatures = getDocBaseMap(\r
+               Map<String, File> docBaseSignatures = getDocBaseMapInCaches(\r
                                DocBaseSignatureStoratage.OLD_FORMAT);\r
 \r
                // ver0.94までは*.favorite.xmlはユーザディレクトリ直下に配備していたが\r
@@ -317,27 +347,45 @@ class PurgeUnusedCache extends StartupSupportForDocBasedData {
 \r
        @Override\r
        public void doStartup() {\r
-               // キャラクターデータディレクトリを走査しdocBaseの識別子の一覧を取得する\r
-               Map<String, File> docBaseSignatures = getDocBaseMap(\r
-                               DocBaseSignatureStoratage.NEW_FORMAT);\r
-               \r
                // キャッシュの保存先を取得する.\r
                UserDataFactory userDataFactory = UserDataFactory.getInstance();\r
                File cacheDir = userDataFactory.getSpecialDataDir("*.ser");\r
                \r
+               // 現在選択されているキャラクターデータディレクトリ上のUUIDの登録を保証する.\r
+               // (character.xmlに対するUUIDの問い合わせ時に登録がなければ登録される.)\r
+               // (現在選択されていないディレクトリについてはUUIDの登録が行われないので、もしデータベースファイルが\r
+               // 作成されていない場合はキャッシュは一旦削除されることになる.)\r
+               getDocBaseMapInCaches(DocBaseSignatureStoratage.NEW_FORMAT);\r
+               \r
                // キャッシュ上にあるDocBaseのUUID表現で始まる*.serを列挙\r
                String[] suffixes = {\r
                                "-character.xml-cache.ser", // character.xmlのキャッシュ\r
                                "-workingset.ser", // 作業状態のキャッシュ\r
                                "-favorites.ser" // お気に入りのキャッシュ\r
                                };\r
+\r
+               // UUIDからURIの索引\r
+               final Map<String, URI> mangledURIMap = userDataFactory.getMangledNameMap(cacheDir);\r
+\r
+               // キャッシュファイルで使用されているUUIDの示す実体のURIが実在しない場合は、そのキャッシュは不要と見なす.\r
                for (String suffix : suffixes) {\r
                        Map<String, File> caches = getUUIDMangledNamedMap(cacheDir, suffix);\r
                        for (Map.Entry<String, File> cacheEntry : caches.entrySet()) {\r
                                String mangledUUID = cacheEntry.getKey();\r
                                File cacheFile = cacheEntry.getValue();\r
                                try {\r
-                                       if ( !docBaseSignatures.containsKey(mangledUUID)) {\r
+                                       URI uri = mangledURIMap.get(mangledUUID);\r
+                                       boolean remove = true;\r
+                                       if (uri != null) {\r
+                                               File characterXmlFile = new File(uri);\r
+                                               if (characterXmlFile.exists() || characterXmlFile.isFile()) {\r
+                                                       // UUIDデータベースに登録があり、且つ、\r
+                                                       // character.xmlが実在する場合のみ削除しない.\r
+                                                       remove = false;\r
+                                               }\r
+                                       }\r
+                                       if (remove) {\r
+                                               // キャッシュファイルを削除する.\r
                                                boolean result = cacheFile.delete();\r
                                                logger.log(Level.INFO, "purge unused cache: " + cacheFile\r
                                                                + "/succeeded=" + result);\r
index 73ca41f..7cc8428 100644 (file)
@@ -17,6 +17,8 @@ import java.io.File;
 import java.util.Collections;\r
 import java.util.Properties;\r
 import java.util.Set;\r
+import java.util.logging.Level;\r
+import java.util.logging.Logger;\r
 \r
 import javax.swing.AbstractAction;\r
 import javax.swing.Action;\r
@@ -25,6 +27,7 @@ import javax.swing.BorderFactory;
 import javax.swing.Box;\r
 import javax.swing.InputMap;\r
 import javax.swing.JButton;\r
+import javax.swing.JCheckBox;\r
 import javax.swing.JComponent;\r
 import javax.swing.JDialog;\r
 import javax.swing.JFrame;\r
@@ -55,21 +58,40 @@ import charactermanaj.util.SetupLocalization;
 public class AppConfigDialog extends JDialog {\r
 \r
        private static final long serialVersionUID = 1L;\r
+       \r
+       private static final Logger logger = Logger.getLogger(AppConfigDialog.class.getName());\r
 \r
        private AppConfigTableModel appConfigTableModel;\r
        \r
        private JTable appConfigTable;\r
        \r
+       private JCheckBox chkResetDoNotAskAgain;\r
+       \r
+       private RecentCharactersDir recentCharactersDir;\r
+       \r
        public AppConfigDialog(JFrame parent) {\r
                super(parent, true);\r
-               \r
-               setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);\r
-               addWindowListener(new WindowAdapter() {\r
-                       @Override\r
-                       public void windowClosing(WindowEvent e) {\r
-                               onClose();\r
-                       }\r
-               });\r
+               try {\r
+                       setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);\r
+                       addWindowListener(new WindowAdapter() {\r
+                               @Override\r
+                               public void windowClosing(WindowEvent e) {\r
+                                       onClose();\r
+                               }\r
+                       });\r
+\r
+                       initComponent();\r
+                       \r
+                       loadData();\r
+                       \r
+               } catch (RuntimeException ex) {\r
+                       logger.log(Level.SEVERE, "appConfig construct failed.", ex);\r
+                       dispose();\r
+                       throw ex;\r
+               }\r
+       }\r
+       \r
+       private void initComponent() {\r
                \r
                Properties strings = LocalizedResourcePropertyLoader.getInstance()\r
                                .getLocalizedProperties("languages/appconfigdialog");\r
@@ -106,12 +128,27 @@ public class AppConfigDialog extends JDialog {
                        }\r
                };\r
                \r
+               chkResetDoNotAskAgain = new JCheckBox(strings.getProperty("chk.askForCharactersDir"));\r
+\r
                gbc.gridx = 0;\r
                gbc.gridy = 0;\r
                gbc.gridheight = 1;\r
                gbc.gridwidth = 3;\r
                gbc.anchor = GridBagConstraints.WEST;\r
                gbc.fill = GridBagConstraints.NONE;\r
+               gbc.insets = new Insets(0, 0, 0, 0);\r
+               gbc.ipadx = 0;\r
+               gbc.ipady = 0;\r
+               gbc.weightx = 1.;\r
+               gbc.weighty = 0.;\r
+               btnPanel.add(chkResetDoNotAskAgain, gbc);\r
+               \r
+               gbc.gridx = 0;\r
+               gbc.gridy = 1;\r
+               gbc.gridheight = 1;\r
+               gbc.gridwidth = 3;\r
+               gbc.anchor = GridBagConstraints.WEST;\r
+               gbc.fill = GridBagConstraints.NONE;\r
                gbc.insets = new Insets(3, 3, 3, 3);\r
                gbc.ipadx = 0;\r
                gbc.ipady = 0;\r
@@ -120,7 +157,7 @@ public class AppConfigDialog extends JDialog {
                btnPanel.add(new JButton(actLocalization), gbc);\r
 \r
                gbc.gridx = 0;\r
-               gbc.gridy = 1;\r
+               gbc.gridy = 2;\r
                gbc.gridheight = 1;\r
                gbc.gridwidth = 1;\r
                gbc.fill = GridBagConstraints.BOTH;\r
@@ -128,12 +165,12 @@ public class AppConfigDialog extends JDialog {
                gbc.weighty = 0.;\r
                btnPanel.add(Box.createHorizontalGlue(), gbc);\r
                \r
-               gbc.gridx = Main.isMacOSX() ? 2 : 1;\r
+               gbc.gridx = Main.isLinuxOrMacOSX() ? 2 : 1;\r
                gbc.weightx = 0.;\r
                JButton btnApply = new JButton(actApply);\r
                btnPanel.add(btnApply, gbc);\r
                \r
-               gbc.gridx = Main.isMacOSX() ? 1 : 2;\r
+               gbc.gridx = Main.isLinuxOrMacOSX() ? 1 : 2;\r
                gbc.weightx = 0.;\r
                JButton btnCancel = new JButton(actCancel);\r
                btnPanel.add(btnCancel, gbc);\r
@@ -141,7 +178,7 @@ public class AppConfigDialog extends JDialog {
                add(btnPanel, BorderLayout.SOUTH);\r
                \r
                setSize(350, 400);\r
-               setLocationRelativeTo(parent);\r
+               setLocationRelativeTo(getParent());\r
                \r
                // Notes\r
                JLabel lblCaution = new JLabel(strings.getProperty("caution"), JLabel.CENTER);\r
@@ -151,8 +188,6 @@ public class AppConfigDialog extends JDialog {
                \r
                // Model\r
                appConfigTableModel = new AppConfigTableModel();\r
-               Properties original = AppConfig.getInstance().getProperties();\r
-               appConfigTableModel.initModel(original);\r
 \r
                // JTable\r
                AppConfig appConfig = AppConfig.getInstance();\r
@@ -207,6 +242,27 @@ public class AppConfigDialog extends JDialog {
                am.put("closeAppConfigDialog", actCancel);\r
        }\r
        \r
+       private void loadData() {\r
+               Properties original = AppConfig.getInstance().getProperties();\r
+               appConfigTableModel.initModel(original);\r
+\r
+               try {\r
+                       recentCharactersDir = RecentCharactersDir.load();\r
+\r
+                       if (recentCharactersDir != null) {\r
+                               File lastUseCharactersDir = recentCharactersDir.getLastUseCharacterDir();\r
+                               boolean enableLastUseCharacterDir = lastUseCharactersDir != null && lastUseCharactersDir.isDirectory(); \r
+                               boolean doNotAskAgain = enableLastUseCharacterDir && recentCharactersDir.isDoNotAskAgain();\r
+                               chkResetDoNotAskAgain.setEnabled(enableLastUseCharacterDir);\r
+                               chkResetDoNotAskAgain.setSelected(!doNotAskAgain);\r
+                       }\r
+\r
+               } catch (Exception ex) {\r
+                       recentCharactersDir = null;\r
+                       logger.log(Level.WARNING, "RecentCharactersDir load failed.", ex);\r
+               }\r
+       }\r
+\r
        protected void onSetupLocalization() {\r
                Properties strings = LocalizedResourcePropertyLoader.getInstance()\r
                        .getLocalizedProperties("languages/appconfigdialog");\r
@@ -269,15 +325,26 @@ public class AppConfigDialog extends JDialog {
                        return;\r
                }\r
 \r
-               // アプリケーション設定を更新し、保存する.\r
-               AppConfig appConfig = AppConfig.getInstance();\r
-               appConfig.update(props);\r
                try {\r
+                       // アプリケーション設定を更新し、保存する.\r
+                       AppConfig appConfig = AppConfig.getInstance();\r
+                       appConfig.update(props);\r
                        appConfig.saveConfig();\r
+\r
+                       // キャラクターデータディレクトリの起動時の選択\r
+                       if (chkResetDoNotAskAgain.isEnabled()) {\r
+                               boolean doNotAskAgain = !chkResetDoNotAskAgain.isSelected();\r
+                               if (doNotAskAgain != recentCharactersDir.isDoNotAskAgain()) {\r
+                                       recentCharactersDir.setDoNotAskAgain(doNotAskAgain);\r
+                                       recentCharactersDir.saveRecents();\r
+                               }\r
+                       }\r
+\r
                } catch (Exception ex) {\r
                        ErrorMessageHelper.showErrorDialog(this, ex);\r
+                       return;\r
                }\r
-\r
+               \r
                // アプリケーションの再起動が必要なことを示すダイアログを表示する.\r
                String message = strings.getProperty("caution");\r
                JOptionPane.showMessageDialog(this, message);\r
index e7c33eb..d5ef8b6 100644 (file)
@@ -253,21 +253,21 @@ public class ExportWizardDialog extends JDialog {
                gbc.weighty = 0.;\r
                btnPanel.add(Box.createHorizontalGlue(), gbc);\r
                \r
-               gbc.gridx = Main.isMacOSX() ? 2 : 1;\r
+               gbc.gridx = Main.isLinuxOrMacOSX() ? 2 : 1;\r
                gbc.gridy = 0;\r
                gbc.weightx = 0.;\r
                btnPanel.add(new JButton(this.actPrev), gbc);\r
                \r
-               gbc.gridx = Main.isMacOSX() ? 3 : 2;\r
+               gbc.gridx = Main.isLinuxOrMacOSX() ? 3 : 2;\r
                gbc.gridy = 0;\r
                JButton btnNext = new JButton(this.actNext);\r
                btnPanel.add(btnNext, gbc);\r
                \r
-               gbc.gridx = Main.isMacOSX() ? 4 : 3;\r
+               gbc.gridx = Main.isLinuxOrMacOSX() ? 4 : 3;\r
                gbc.gridy = 0;\r
                btnPanel.add(new JButton(this.actFinish), gbc);\r
 \r
-               gbc.gridx = Main.isMacOSX() ? 1 : 4;\r
+               gbc.gridx = Main.isLinuxOrMacOSX() ? 1 : 4;\r
                gbc.gridy = 0;\r
                JButton btnCancel = new JButton(actCancel);\r
                btnPanel.add(btnCancel, gbc);\r
index d7d4df5..2bba6bd 100644 (file)
@@ -323,21 +323,21 @@ public class ImportWizardDialog extends JDialog {
                gbc.weighty = 0.;\r
                btnPanel.add(Box.createHorizontalGlue(), gbc);\r
                \r
-               gbc.gridx = Main.isMacOSX() ? 2 : 1;\r
+               gbc.gridx = Main.isLinuxOrMacOSX() ? 2 : 1;\r
                gbc.gridy = 0;\r
                gbc.weightx = 0.;\r
                btnPanel.add(new JButton(this.actPrev), gbc);\r
                \r
-               gbc.gridx = Main.isMacOSX() ? 3 : 2;\r
+               gbc.gridx = Main.isLinuxOrMacOSX() ? 3 : 2;\r
                gbc.gridy = 0;\r
                JButton btnNext = new JButton(this.actNext);\r
                btnPanel.add(btnNext, gbc);\r
                \r
-               gbc.gridx = Main.isMacOSX() ? 4 : 3;\r
+               gbc.gridx = Main.isLinuxOrMacOSX() ? 4 : 3;\r
                gbc.gridy = 0;\r
                btnPanel.add(new JButton(this.actFinish), gbc);\r
 \r
-               gbc.gridx = Main.isMacOSX() ? 1 : 4;\r
+               gbc.gridx = Main.isLinuxOrMacOSX() ? 1 : 4;\r
                gbc.gridy = 0;\r
                JButton btnCancel = new JButton(actCancel);\r
                btnPanel.add(btnCancel, gbc);\r
index 4fb1354..8dd9c01 100644 (file)
@@ -329,12 +329,12 @@ public class PartsManageDialog extends JDialog {
                gbc.weightx = 1.;\r
                btnPanel.add(Box.createHorizontalGlue(), gbc);\r
                \r
-               gbc.gridx = Main.isMacOSX() ? 2 : 1;\r
+               gbc.gridx = Main.isLinuxOrMacOSX() ? 2 : 1;\r
                gbc.gridy = 0;\r
                gbc.weightx = 0.;\r
                btnPanel.add(new JButton(actOK), gbc);\r
 \r
-               gbc.gridx = Main.isMacOSX() ? 1 : 2;\r
+               gbc.gridx = Main.isLinuxOrMacOSX() ? 1 : 2;\r
                gbc.gridy = 0;\r
                gbc.weightx = 0.;\r
                btnPanel.add(new JButton(actClose), gbc);\r
index 543382e..9d3a2cc 100644 (file)
@@ -311,12 +311,12 @@ public class ProfileEditDialog extends JDialog {
                gbc.fill = GridBagConstraints.BOTH;\r
                buttonsPanel.add(Box.createGlue(), gbc);\r
 \r
-               gbc.gridx = Main.isMacOSX() ? 3 : 2;\r
+               gbc.gridx = Main.isLinuxOrMacOSX() ? 3 : 2;\r
                gbc.gridy = 0;\r
                gbc.weightx = 0.;\r
                buttonsPanel.add(btnOK, gbc);\r
                \r
-               gbc.gridx = Main.isMacOSX() ? 2 : 3;\r
+               gbc.gridx = Main.isLinuxOrMacOSX() ? 2 : 3;\r
                gbc.gridy = 0;\r
                buttonsPanel.add(btnCancel, gbc);\r
                \r
index 2ddc85d..509b011 100644 (file)
@@ -266,7 +266,7 @@ public final class ProfileListManager {
                        loadCharacterData(characterData);\r
                        loadFavorites(characterData);\r
 \r
-               } catch (IOException ex) {\r
+               } catch (Exception ex) {\r
                        ErrorMessageHelper.showErrorDialog(null, ex);\r
                        characterData = null;\r
                }\r
index 5caed6c..a78cc94 100644 (file)
@@ -423,7 +423,7 @@ public class ProfileSelectorDialog extends JDialog {
                \r
                btnOK = new JButton(actOK);\r
                JButton btnCancel = new JButton(actCancel);\r
-               if (Main.isMacOSX()) {\r
+               if (Main.isLinuxOrMacOSX()) {\r
                        btnPanel.add(btnCancel);\r
                        btnPanel.add(btnOK);\r
                } else {\r
diff --git a/src/charactermanaj/ui/RecentCharactersDir.java b/src/charactermanaj/ui/RecentCharactersDir.java
new file mode 100644 (file)
index 0000000..3846aa4
--- /dev/null
@@ -0,0 +1,93 @@
+package charactermanaj.ui;\r
+\r
+import java.io.File;\r
+import java.io.IOException;\r
+import java.io.Serializable;\r
+import java.util.ArrayList;\r
+import java.util.Iterator;\r
+\r
+import charactermanaj.util.UserData;\r
+import charactermanaj.util.UserDataFactory;\r
+\r
+/**\r
+ * 最後に使用したキャラクターデータディレクトリと、その履歴情報.<br>\r
+ * @author seraphy\r
+ */\r
+public class RecentCharactersDir implements Serializable {\r
+       \r
+       private static final long serialVersionUID = -5274310741380875405L;\r
+       \r
+       /**\r
+        * ファイル名\r
+        */\r
+       public static final String FILENAME = "recent-characterdirs.ser";\r
+\r
+       /**\r
+        * 最後に使用したディレクトリ\r
+        */\r
+       private File lastUseCharacterDir;\r
+       \r
+       /**\r
+        * 過去に使用したディレクトリ情報\r
+        */\r
+       private ArrayList<File> recentCharacterDirs = new ArrayList<File>();\r
+       \r
+       /**\r
+        * ディレクトリの問い合わせ不要フラグ.\r
+        */\r
+       private boolean doNotAskAgain;\r
+\r
+       \r
+       public ArrayList<File> getRecentCharacterDirs() {\r
+               return recentCharacterDirs;\r
+       }\r
+       \r
+       public void setLastUseCharacterDir(File lastUseCharacterDir) {\r
+               this.lastUseCharacterDir = lastUseCharacterDir;\r
+       }\r
+       \r
+       public File getLastUseCharacterDir() {\r
+               return lastUseCharacterDir;\r
+       }\r
+\r
+       public void clrar() {\r
+               doNotAskAgain = false;\r
+               lastUseCharacterDir = null;\r
+               recentCharacterDirs.clear();\r
+       }\r
+       \r
+       public boolean isDoNotAskAgain() {\r
+               return doNotAskAgain;\r
+       }\r
+       \r
+       public void setDoNotAskAgain(boolean doNotAskAgain) {\r
+               this.doNotAskAgain = doNotAskAgain;\r
+       }\r
+\r
+       public static RecentCharactersDir load() throws IOException {\r
+               UserDataFactory factory = UserDataFactory.getInstance();\r
+               UserData recentCharDirs =  factory.getUserData(FILENAME);\r
+               if (recentCharDirs.exists()) {\r
+                       return (RecentCharactersDir) recentCharDirs.load();\r
+               }\r
+               return null;\r
+       }\r
+       \r
+       public void saveRecents() throws IOException {\r
+               if (lastUseCharacterDir != null) {\r
+                       // 既存のリストに現在の選択があれば、一旦削除する.\r
+                       Iterator<File> ite = recentCharacterDirs.iterator();\r
+                       while (ite.hasNext()) {\r
+                               File file = ite.next();\r
+                               if (lastUseCharacterDir.equals(file)) {\r
+                                       ite.remove();\r
+                               }\r
+                       }\r
+                       // 現在の選択を先頭にする.\r
+                       recentCharacterDirs.add(0, lastUseCharacterDir);\r
+               }\r
+               UserDataFactory factory = UserDataFactory.getInstance();\r
+               UserData recentCharDirs =  factory.getUserData(FILENAME);\r
+               recentCharDirs.save(this);\r
+       }\r
+}
\ No newline at end of file
index 916db52..6562959 100644 (file)
@@ -3,6 +3,7 @@ package charactermanaj.ui;
 import java.awt.BorderLayout;\r
 import java.awt.Container;\r
 import java.awt.Dimension;\r
+import java.awt.Font;\r
 import java.awt.GridBagConstraints;\r
 import java.awt.GridBagLayout;\r
 import java.awt.Insets;\r
@@ -14,14 +15,13 @@ import java.awt.event.KeyEvent;
 import java.awt.event.WindowAdapter;\r
 import java.awt.event.WindowEvent;\r
 import java.io.File;\r
-import java.io.IOException;\r
-import java.io.Serializable;\r
 import java.util.ArrayList;\r
-import java.util.Iterator;\r
+import java.util.Properties;\r
 import java.util.logging.Level;\r
 import java.util.logging.Logger;\r
 \r
 import javax.swing.AbstractAction;\r
+import javax.swing.BorderFactory;\r
 import javax.swing.Box;\r
 import javax.swing.JButton;\r
 import javax.swing.JCheckBox;\r
@@ -34,10 +34,10 @@ import javax.swing.JPanel;
 import javax.swing.JRootPane;\r
 import javax.swing.KeyStroke;\r
 \r
+import charactermanaj.Main;\r
 import charactermanaj.model.io.CharacterDataPersistent;\r
 import charactermanaj.util.ErrorMessageHelper;\r
-import charactermanaj.util.UserData;\r
-import charactermanaj.util.UserDataFactory;\r
+import charactermanaj.util.LocalizedResourcePropertyLoader;\r
 \r
 public class SelectCharatersDirDialog extends JDialog {\r
 \r
@@ -73,8 +73,8 @@ public class SelectCharatersDirDialog extends JDialog {
                return doNotAskAgain;\r
        }\r
        \r
-       protected SelectCharatersDirDialog(RecentCharactersDir recentCharactersDir) {\r
-               super((JFrame) null, true);\r
+       protected SelectCharatersDirDialog(JFrame parent, RecentCharactersDir recentCharactersDir) {\r
+               super(parent, true);\r
                try {\r
                        if (recentCharactersDir == null) {\r
                                throw new IllegalArgumentException("recentCharactersDirにnullは指定できません。");\r
@@ -98,31 +98,34 @@ public class SelectCharatersDirDialog extends JDialog {
        }\r
        \r
        private void initComponent() {\r
+               Properties strings = LocalizedResourcePropertyLoader.getInstance()\r
+                       .getLocalizedProperties("languages/selectCharatersDirDialog");\r
+               \r
                Container contentPane = getContentPane();\r
-               contentPane.setLayout(new BorderLayout());\r
+               contentPane.setLayout(new BorderLayout(3, 3));\r
                \r
-               AbstractAction actOk = new AbstractAction("OK") {\r
+               AbstractAction actOk = new AbstractAction(strings.getProperty("btn.ok")) {\r
                        private static final long serialVersionUID = 1L;\r
                        public void actionPerformed(ActionEvent e) {\r
                                onOK();\r
                        }\r
                };\r
                \r
-               AbstractAction actClose = new AbstractAction("cancel") {\r
+               AbstractAction actClose = new AbstractAction(strings.getProperty("btn.cancel")) {\r
                        private static final long serialVersionUID = 1L;\r
                        public void actionPerformed(ActionEvent e) {\r
                                onClose();\r
                        }\r
                };\r
 \r
-               AbstractAction actBrowse = new AbstractAction("browse") {\r
+               AbstractAction actBrowse = new AbstractAction(strings.getProperty("btn.chooseDir")) {\r
                        private static final long serialVersionUID = 1L;\r
                        public void actionPerformed(ActionEvent e) {\r
                                onBrowse();\r
                        }\r
                };\r
 \r
-               AbstractAction actRemoveRecent = new AbstractAction("履歴の消去") {\r
+               AbstractAction actRemoveRecent = new AbstractAction(strings.getProperty("btn.clearRecentList")) {\r
                        private static final long serialVersionUID = 1L;\r
                        public void actionPerformed(ActionEvent e) {\r
                                onRemoveRecent();\r
@@ -156,28 +159,32 @@ public class SelectCharatersDirDialog extends JDialog {
                btnBroseForDir.addFocusListener(focusAdapter);\r
 \r
 \r
-               JLabel lbl = new JLabel("キャラクターデータを格納するディレクトリを選択してください。");\r
+               JPanel dirPanel = new JPanel(new BorderLayout(3, 3));\r
+               dirPanel.setBorder(BorderFactory.createEmptyBorder(3, 10, 3, 3));\r
+               \r
+               JLabel lbl = new JLabel(strings.getProperty("caption"), JLabel.CENTER);\r
+               lbl.setFont(lbl.getFont().deriveFont(Font.BOLD));\r
+               lbl.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0));\r
                Dimension dim = lbl.getPreferredSize();\r
-               dim.width = 500;\r
+               dim.width = Integer.parseInt(strings.getProperty("width"));\r
                lbl.setPreferredSize(dim);\r
-               contentPane.add(lbl, BorderLayout.NORTH);\r
+               dirPanel.add(lbl, BorderLayout.NORTH);\r
 \r
-               JPanel dirPanel = new JPanel(new BorderLayout());\r
-               \r
                combDir = new JComboBox();\r
                combDir.setEditable(true);\r
                \r
                dirPanel.add(combDir, BorderLayout.CENTER);\r
                \r
+               dirPanel.add(new JLabel(strings.getProperty("lbl.dir")), BorderLayout.WEST);\r
                dirPanel.add(btnBroseForDir, BorderLayout.EAST);\r
                \r
-               contentPane.add(dirPanel, BorderLayout.CENTER);\r
+               contentPane.add(dirPanel, BorderLayout.NORTH);\r
                \r
                JPanel btnPanel = new JPanel();\r
                GridBagLayout gbl = new GridBagLayout();\r
                btnPanel.setLayout(gbl);\r
                \r
-               chkDoNotAsk = new JCheckBox("次回から、このディレクトリを使用する.");\r
+               chkDoNotAsk = new JCheckBox(strings.getProperty("chk.doNotAskAgein"));\r
                chkDoNotAsk.setSelected(recentCharactersDir.isDoNotAskAgain());\r
                \r
                GridBagConstraints gbc = new GridBagConstraints();\r
@@ -212,7 +219,7 @@ public class SelectCharatersDirDialog extends JDialog {
                \r
                btnPanel.add(Box.createGlue(), gbc);\r
 \r
-               gbc.gridx = 2;\r
+               gbc.gridx = Main.isLinuxOrMacOSX() ? 3 : 2;\r
                gbc.gridy = 1;\r
                gbc.gridwidth = 1;\r
                gbc.gridheight = 1;\r
@@ -222,7 +229,7 @@ public class SelectCharatersDirDialog extends JDialog {
                btnPanel.add(btnOK, gbc);\r
 \r
 \r
-               gbc.gridx = 3;\r
+               gbc.gridx = Main.isLinuxOrMacOSX() ? 2 : 3;\r
                gbc.gridy = 1;\r
                gbc.gridwidth = 1;\r
                gbc.gridheight = 1;\r
@@ -241,7 +248,8 @@ public class SelectCharatersDirDialog extends JDialog {
                \r
                btnPanel.add(Box.createGlue(), gbc);\r
 \r
-               setTitle("キャラクターなんとかJ");\r
+               setTitle(strings.getProperty("title"));\r
+               setResizable(false);\r
                \r
                contentPane.add(btnPanel, BorderLayout.SOUTH);\r
                pack();\r
@@ -417,12 +425,13 @@ public class SelectCharatersDirDialog extends JDialog {
                        recentChars.setDoNotAskAgain(false); // 不正である場合は「再度問い合わせ無し」をリセットする.\r
                }\r
 \r
-               SelectCharatersDirDialog dlg = new SelectCharatersDirDialog(recentChars);\r
+               File selectedCharacterDir;\r
+               SelectCharatersDirDialog dlg = new SelectCharatersDirDialog(null, recentChars);\r
                dlg.setDefaultCharactersDir(defaultCharacterDir);\r
                dlg.setRecents();\r
                dlg.setVisible(true);\r
                \r
-               File selectedCharacterDir = dlg.getSelectedCharacterDir();\r
+               selectedCharacterDir = dlg.getSelectedCharacterDir();\r
                if (selectedCharacterDir != null) {\r
                        recentChars.setLastUseCharacterDir(selectedCharacterDir);\r
                        try {\r
@@ -435,92 +444,4 @@ public class SelectCharatersDirDialog extends JDialog {
                }\r
                return selectedCharacterDir;\r
        }\r
-       \r
-       public static void main(String[] args) {\r
-               getCharacterDir(new File("c:\\temp"));\r
-       }\r
-       \r
-}\r
-\r
-/**\r
- * 最後に使用したキャラクターデータディレクトリと、その履歴情報.<br>\r
- * @author seraphy\r
- */\r
-class RecentCharactersDir implements Serializable {\r
-       \r
-       private static final long serialVersionUID = -5274310741380875405L;\r
-\r
-       /**\r
-        * ファイル名\r
-        */\r
-       public static final String FILENAME = "recent-characterdirs.ser";\r
-\r
-       /**\r
-        * 最後に使用したディレクトリ\r
-        */\r
-       private File lastUseCharacterDir;\r
-       \r
-       /**\r
-        * 過去に使用したディレクトリ情報\r
-        */\r
-       private ArrayList<File> recentCharacterDirs = new ArrayList<File>();\r
-       \r
-       /**\r
-        * ディレクトリの問い合わせ不要フラグ.\r
-        */\r
-       private boolean doNotAskAgain;\r
-\r
-       \r
-       public ArrayList<File> getRecentCharacterDirs() {\r
-               return recentCharacterDirs;\r
-       }\r
-       \r
-       public void setLastUseCharacterDir(File lastUseCharacterDir) {\r
-               this.lastUseCharacterDir = lastUseCharacterDir;\r
-       }\r
-       \r
-       public File getLastUseCharacterDir() {\r
-               return lastUseCharacterDir;\r
-       }\r
-\r
-       public void clrar() {\r
-               doNotAskAgain = false;\r
-               lastUseCharacterDir = null;\r
-               recentCharacterDirs.clear();\r
-       }\r
-       \r
-       public boolean isDoNotAskAgain() {\r
-               return doNotAskAgain;\r
-       }\r
-       \r
-       public void setDoNotAskAgain(boolean doNotAskAgain) {\r
-               this.doNotAskAgain = doNotAskAgain;\r
-       }\r
-\r
-       public static RecentCharactersDir load() throws IOException {\r
-               UserDataFactory factory = UserDataFactory.getInstance();\r
-               UserData recentCharDirs =  factory.getUserData(FILENAME);\r
-               if (recentCharDirs.exists()) {\r
-                       return (RecentCharactersDir) recentCharDirs.load();\r
-               }\r
-               return null;\r
-       }\r
-       \r
-       protected void saveRecents() throws IOException {\r
-               if (lastUseCharacterDir != null) {\r
-                       // 既存のリストに現在の選択があれば、一旦削除する.\r
-                       Iterator<File> ite = recentCharacterDirs.iterator();\r
-                       while (ite.hasNext()) {\r
-                               File file = ite.next();\r
-                               if (lastUseCharacterDir.equals(file)) {\r
-                                       ite.remove();\r
-                               }\r
-                       }\r
-                       // 現在の選択を先頭にする.\r
-                       recentCharacterDirs.add(0, lastUseCharacterDir);\r
-               }\r
-               UserDataFactory factory = UserDataFactory.getInstance();\r
-               UserData recentCharDirs =  factory.getUserData(FILENAME);\r
-               recentCharDirs.save(this);\r
-       }\r
 }\r
index 826e96a..bbf7a7a 100644 (file)
@@ -115,64 +115,6 @@ public final class ConfigurationDirUtilities {
                return applicationBaseDir;\r
        }\r
        \r
-//     /**\r
-//      * 全ユーザー共通のキャラクターデータディレクトリを取得する.<br>\r
-//      * 全ユーザー共通のキャラクターデータディレクトリが実在しない場合はnullを返す.<br>\r
-//      * アプリケーション設定、システムプロパティ「character.dir」、アプリケーションディレクトリ上の「characters」の優先順で検索される.<br>\r
-//      * @return 全ユーザー共通のキャラクターデータディレクトリ、またはnull\r
-//      */\r
-//     public File getSystemCharactersDir() {\r
-//             \r
-//             File applicationBaseDir = ConfigurationDirUtilities.getApplicationBaseDir();\r
-//             File[] candidates = new File[] {\r
-//                             getAbsoluteFile(applicationBaseDir, getCommonCharacterDataDir()),\r
-//                             getAbsoluteFile(applicationBaseDir, System.getProperty(COMMON_CHARACTER_DIR_PROPERTY_NAME)),\r
-//                             getAbsoluteFile(applicationBaseDir, "characters"),\r
-//             };\r
-//             File systemCharacterDir = null;\r
-//             for (File dir : candidates) {\r
-//                     // 候補を順に検査し最初に合格したディレクトリを採用する\r
-//                     if (dir == null) {\r
-//                             continue;\r
-//                     }\r
-//                     if (dir.exists() && dir.isDirectory()) {\r
-//                             systemCharacterDir = dir;\r
-//                             break;\r
-//                     }\r
-//             }\r
-//             return systemCharacterDir;\r
-//     }\r
-//     \r
-//     /**\r
-//      * 指定したファイルが絶対パスであれば、それを返す.<br>\r
-//      * 絶対パスでなければベースディレクトリを親とした相対パスとして正規化し、その絶対パスを返す.<br>\r
-//      * fileがnullまたは空文字の場合はnullを返す.<br>\r
-//      * baseDirとfileを連結し正規化するときにエラーが発生した場合はnullを返す.<br>\r
-//      * @param baseDir ベースディレクトリ、fileが相対パスであれば、fileの親となる.\r
-//      * @param file ファイル、nullまたは空文字も可\r
-//      * @return 絶対パス、もしくはnull\r
-//      */\r
-//     protected File getAbsoluteFile(File baseDir, String file) {\r
-//             if (baseDir == null) {\r
-//                     throw new IllegalArgumentException();\r
-//             }\r
-//             if (file == null || file.trim().length() == 0) {\r
-//                     return null;\r
-//             }\r
-//             File result = new File(file);\r
-//             if (!result.isAbsolute()) {\r
-//                     try {\r
-//                             // ファイル名が絶対パスでない場合はベースディレクトリからの相対にする\r
-//                             result = new File(baseDir, file.trim()).getCanonicalFile();\r
-//                             \r
-//                     } catch (IOException ex) {\r
-//                             ex.printStackTrace();\r
-//                             return null;\r
-//                     }\r
-//             }\r
-//             return result;\r
-//     }\r
-\r
        /**\r
         * デフォルトのユーザー固有のキャラクターデータディレクトリを取得する.<br>\r
         * ユーザー固有のキャラクターディレクトリがまだ存在しない場合は作成される.<br>\r
diff --git a/src/charactermanaj/util/FileMappedProperties.java b/src/charactermanaj/util/FileMappedProperties.java
new file mode 100644 (file)
index 0000000..b286893
--- /dev/null
@@ -0,0 +1,241 @@
+package charactermanaj.util;\r
+\r
+import java.io.ByteArrayInputStream;\r
+import java.io.ByteArrayOutputStream;\r
+import java.io.File;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.RandomAccessFile;\r
+import java.nio.ByteBuffer;\r
+import java.nio.channels.FileChannel;\r
+import java.sql.Timestamp;\r
+import java.util.AbstractMap;\r
+import java.util.AbstractSet;\r
+import java.util.Iterator;\r
+import java.util.Map;\r
+import java.util.Properties;\r
+import java.util.Set;\r
+\r
+/**\r
+ * ファイルに保存されるPropertiesアクセスの実装.<br>\r
+ * ファイルの読み込みまたは書き込みがあると、それ以降、排他制御される.<br>\r
+ * ファイルを閉じた後は読み込み・書き込みはできず、プロパティの更新もできなく、IllegalStateException例外となります.<br>\r
+ * ただし、閉じた後でもプロパティの取得は可能です.<br> \r
+ * @author seraphy\r
+ */\r
+public class FileMappedProperties extends AbstractMap<String, String> {\r
+\r
+       /**\r
+        * データベースファイル\r
+        */\r
+       private File file;\r
+       \r
+       /**\r
+        * データベースファイルへのランダムアクセス\r
+        */\r
+       private RandomAccessFile accessFile;\r
+       \r
+       /**\r
+        * ランダムアクセスファイルへのファイルチャネル(排他制御のため)\r
+        */\r
+       private FileChannel channel;\r
+       \r
+       /**\r
+        * プロパティ実体\r
+        */\r
+       private Properties props = new Properties();\r
+       \r
+       /**\r
+        * 変更フラグ\r
+        */\r
+       private boolean modified;\r
+       \r
+       \r
+       /**\r
+        * データベースとなるファイルを指定してプロパティを構築する.<br>\r
+        * 呼び出された時点でファイルがなければ作成される.<br> \r
+        * @param file ファイル\r
+        * @throws IOException 失敗\r
+        */\r
+       public FileMappedProperties(File file) throws IOException {\r
+               if (file == null) {\r
+                       throw new IllegalArgumentException();\r
+               }\r
+               this.file = file;\r
+               this.accessFile = new RandomAccessFile(file, "rw");\r
+       }\r
+       \r
+       /**\r
+        * データベースとなるファイル\r
+        * @return データベースとなるファイル\r
+        */\r
+       public File getFile() {\r
+               return file;\r
+       }\r
+\r
+       /**\r
+        * 変更されているか?<br>\r
+        * putまたはremoveによって設定される.<br>\r
+        * save、load、clearのいずれかによりリセットされる.<br>\r
+        * @return 変更されている場合はtrue\r
+        */\r
+       public boolean isModified() {\r
+               return modified;\r
+       }\r
+       \r
+       /**\r
+        * データベースとなるファイルからプロパティを読み取り排他制御をかける.<br>\r
+        * 以降、closeされるまで、ファイルはロックされます.<br>\r
+        * すでに読み込まれている場合は何もしません。(ロックされているので自身以外の書き込みは想定しないため).<br>\r
+        * @throws IOException 失敗\r
+        */\r
+       public void load() throws IOException {\r
+               checkOpen();\r
+               if (channel != null) {\r
+                       // すでにロード済みと見なす.\r
+                       return;\r
+               }\r
+               \r
+               props.clear();\r
+               channel = accessFile.getChannel();\r
+               channel.lock(); // 全域に排他ロック\r
+               \r
+               int siz = (int) channel.size();\r
+               if (siz == 0) {\r
+                       // 空なので読み込まない.\r
+                       return;\r
+               }\r
+               byte[] data = new byte[siz];\r
+               ByteBuffer buf = ByteBuffer.wrap(data);\r
+               channel.read(buf);\r
+               \r
+               InputStream bis = new ByteArrayInputStream(data);\r
+               try {\r
+                       props.loadFromXML(bis);\r
+               } finally {\r
+                       bis.close();\r
+               }\r
+       }\r
+       \r
+       @Override\r
+       public void clear() {\r
+               super.clear();\r
+               modified = false;\r
+       }\r
+       \r
+       /**\r
+        * 現在のプロパティの内容をファイルに書き戻します.<br>\r
+        * ファイルがロックされていない場合はロックされ、以降、closeされるまでロックを維持します.<br>\r
+        * 呼び出される都度、ファイルを一括更新します.<br>\r
+        * @throws IOException 失敗\r
+        */\r
+       public void save() throws IOException {\r
+               checkOpen();\r
+               if (channel == null) {\r
+                       channel = accessFile.getChannel();\r
+                       channel.lock(); // 全域に排他ロック\r
+               }\r
+               \r
+               channel.position(0); // 先頭に戻す.\r
+               ByteArrayOutputStream bos = new ByteArrayOutputStream();\r
+               try {\r
+                       String comment = file.getName().toString() + " "\r
+                                       + new Timestamp(System.currentTimeMillis());\r
+                       props.storeToXML(bos, comment);\r
+\r
+               } finally {\r
+                       bos.close();\r
+               }\r
+               \r
+               byte[] data = bos.toByteArray();\r
+               ByteBuffer buf = ByteBuffer.wrap(data);\r
+               channel.write(buf);\r
+               channel.truncate(data.length);\r
+               \r
+               modified = false;\r
+       }\r
+\r
+       /**\r
+        * ファイルを閉じます.<br>\r
+        * ロックされている場合はロックが解除されます.<br>\r
+        * 未保存のプロパティは保存されません.<br>\r
+        * @throws IOException 失敗\r
+        */\r
+       public void close() throws IOException {\r
+               if (channel != null) {\r
+                       channel.close();\r
+                       channel = null;\r
+               }\r
+               if (accessFile != null) {\r
+                       accessFile.close();\r
+                       accessFile = null;\r
+               }\r
+       }\r
+       \r
+       @Override\r
+       protected void finalize() throws Throwable {\r
+               close(); // 念のため\r
+       }\r
+       \r
+       @Override\r
+       public String put(String key, String value) {\r
+               checkOpen();\r
+               String oldValue = (String) props.setProperty(key, value);\r
+               \r
+               // 変更チェック\r
+               if (value != oldValue) {\r
+                       if (value == null || oldValue == null || !value.equals(oldValue)) {\r
+                               // 双方がnullでなく、いずれか一方がnullであるか、equalsが一致しない場合は変更あり.\r
+                               modified = true;\r
+                       }\r
+               }\r
+               return oldValue;\r
+       }\r
+       \r
+       @Override\r
+       public Set<Map.Entry<String, String>> entrySet() {\r
+               final Set<Map.Entry<Object, Object>> entrySet = props.entrySet();\r
+               return new AbstractSet<Map.Entry<String, String>>() {\r
+                       @Override\r
+                       public Iterator<Map.Entry<String, String>> iterator() {\r
+                               final Iterator<Map.Entry<Object, Object>> ite = entrySet.iterator();\r
+                               return new Iterator<Map.Entry<String, String>>() {\r
+                                       public boolean hasNext() {\r
+                                               return ite.hasNext();\r
+                                       }\r
+                                       public Map.Entry<String, String> next() {\r
+                                               final Entry<Object, Object> entry = ite.next();\r
+                                               return new Map.Entry<String, String>() {\r
+                                                       public String getKey() {\r
+                                                               return (String) entry.getKey();\r
+                                                       }\r
+                                                       public String getValue() {\r
+                                                               return (String) entry.getValue();\r
+                                                       }\r
+                                                       public String setValue(String value) {\r
+                                                               checkOpen();\r
+                                                               return (String) entry.setValue(value);\r
+                                                       }\r
+                                               };\r
+                                       }\r
+                                       public void remove() {\r
+                                               checkOpen();\r
+                                               modified = true;\r
+                                               ite.remove();\r
+                                       }\r
+                               };\r
+                       }\r
+                       @Override\r
+                       public int size() {\r
+                               return entrySet.size();\r
+                       }\r
+               };\r
+       }\r
+       \r
+       protected void checkOpen() {\r
+               if (accessFile == null) {\r
+                       throw new IllegalStateException("file is already closed." + file);\r
+               }\r
+       }\r
+       \r
+}\r
index b0d43ee..e722336 100644 (file)
@@ -1,7 +1,14 @@
 package charactermanaj.util;\r
 \r
 import java.io.File;\r
+import java.io.IOException;\r
 import java.net.URI;\r
+import java.util.Arrays;\r
+import java.util.Collection;\r
+import java.util.Collections;\r
+import java.util.HashMap;\r
+import java.util.List;\r
+import java.util.Map;\r
 import java.util.UUID;\r
 import java.util.logging.Level;\r
 import java.util.logging.Logger;\r
@@ -19,11 +26,21 @@ public class UserDataFactory {
        private static final Logger logger = Logger.getLogger(UserDataFactory.class.getName());\r
 \r
        /**\r
+        * MANGLED管理ファイル名\r
+        */\r
+       private static final String META_FILE = "mangled_info.xml";\r
+       \r
+       /**\r
         * シングルトン\r
         */\r
        private static UserDataFactory inst = new UserDataFactory();\r
        \r
        /**\r
+        * MangledNameのキャッシュ\r
+        */\r
+       private HashMap<URI, String> mangledNameMap = new HashMap<URI, String>();\r
+       \r
+       /**\r
         * インスタンスを取得する.\r
         * @return インスタンス\r
         */\r
@@ -86,31 +103,283 @@ public class UserDataFactory {
        /**\r
         * docBaseごとにのハッシュ値を文字列表現化したプレフィックスをもつユーザーデータ保存先を作成する.<br>\r
         * docBaseのURIの圧縮を目的としており、等しいdocBaseは等しいプレフィックスによるようにしている.(暗号化が目的ではない).<br>\r
-        * ハッシュ値はmd5の5バイトで生成されるため、nameを工夫して衝突の確率を軽減するか衝突しても問題ないように考慮することが望ましい.<Br>\r
+        * ハッシュ値はmd5の5バイトで生成されるため、既存のものと衝突した場合は末尾に数値が付与される.<br>\r
         * @param docBase URI、null可\r
         * @param name ファイル名\r
         * @return 保存先\r
         */\r
        public UserData getMangledNamedUserData(URI docBase, String name) {\r
-               String prefix = getMangledNamedPrefix(docBase);\r
-               return getUserData(prefix + "-" + name);\r
+               String mangledName = mangledNameMap.get(docBase);\r
+               if (mangledName == null) {\r
+                       File storeDir = getSpecialDataDir(name);\r
+                       mangledName = registerMangledName(docBase, storeDir);\r
+                       mangledNameMap.put(docBase, mangledName);\r
+               }\r
+               return getUserData(mangledName + "-" + name);\r
+       }\r
+       \r
+       /**\r
+        * ハッシュ化文字列のデータベースファイルをオープンし、現在の登録をすべて読み込む.<br>\r
+        * クローズされるまでデータベースはロックされた状態となる.<br>\r
+        * @param storeDir データベースファイルの格納フォルダ\r
+        * @return ハッシュ化文字列データベース\r
+        * @throws IOException 失敗\r
+        */\r
+       protected FileMappedProperties openMetaFile(File storeDir) throws IOException {\r
+               File metaFile = new File(storeDir, META_FILE);\r
+\r
+               FileMappedProperties mangledProps = new FileMappedProperties(metaFile);\r
+\r
+               // ロードされていなければロードする.\r
+               // (ロードに失敗した場合は空の状態とみなして継続する.)\r
+               try {\r
+                       mangledProps.load();\r
+\r
+               } catch (Exception ex) {\r
+                       logger.log(Level.WARNING, "mangled database is broken."\r
+                                       + mangledProps.getFile(), ex);\r
+               }\r
+               \r
+               return mangledProps;\r
+       }\r
+       \r
+       private static final String URI_KEY_PREFIX = "uri.";\r
+       private static final String MANGLED_NAME_PREFIX = "mangled.";\r
+\r
+\r
+       /**\r
+        * DocBaseのURIを辞書に登録し、そのハッシュ化文字列を返す.<br>\r
+        * ハッシュの衝突を回避するための辞書ファイル「mangled_info.xml」を使用して、\r
+        * 衝突した場合は末尾に連番をふることで補正を行う.<br>\r
+        * すでに登録済みの同じuriの場合は同じハッシュ化文字列を返す.<br>\r
+        * @param docBase URI\r
+        * @param mangledProps 辞書ファイル\r
+        * @return ハッシュ化文字列(衝突した場合は連番が振られる)\r
+        */\r
+       protected String registerMangledName(URI docBase, FileMappedProperties mangledProps) {\r
+               final String noAdjustedMangledName = getNoAdjustedMangledName(docBase);\r
+               String adjustedMangledName = noAdjustedMangledName;\r
+\r
+               // ハッシュ化文字列に対応するURIのリストを取得する.\r
+               // (通常は一個、まれにハッシュが衝突した場合に複数になる.)\r
+               final String mangledLookupKey = "mangled_base." + noAdjustedMangledName;\r
+               String sameMangledURIList = mangledProps.get(mangledLookupKey);\r
+               List<String> uris;\r
+               if (sameMangledURIList != null && sameMangledURIList.length() > 0) {\r
+                       // ハッシュ化文字列に対するURIのリストは空白区切りであるとみなしてリストに変換する.\r
+                       uris = Arrays.asList(sameMangledURIList.split("\\s+"));\r
+               \r
+               } else {\r
+                       // 新規のハッシュ化文字列になる場合は空のリストとする.\r
+                       uris = Collections.emptyList();\r
+                       sameMangledURIList = "";\r
+               }\r
+               \r
+               final String uri = docBase.toASCIIString();\r
+               final String registeredMangledName = mangledProps.get(URI_KEY_PREFIX + uri);\r
+               if (!uris.contains(uri) || registeredMangledName == null || registeredMangledName.length() == 0) {\r
+                       // まだハッシュ化文字列に、そのURIが未登録の場合、\r
+                       // もしくは、そのURIに対するハッシュ化文字列の索引がない場合\r
+                       int pos = uris.indexOf(uri);\r
+                       if (pos < 0) {\r
+                               if (!uris.isEmpty()) {\r
+                                       // まだ未登録である場合、同じUUIDであれば末尾に数値をつける.\r
+                                       // (同一のUUIDがなければ末尾は付与しない)\r
+                                       adjustedMangledName += "_" + uris.size();\r
+                               }\r
+\r
+                               // 登録済みUUIDリストに追加する.\r
+                               if (sameMangledURIList.length() > 0) {\r
+                                       sameMangledURIList += " "; // 空白区切り (URIの文字列表現では空白は含まれないため問題なし)\r
+                               }\r
+                               sameMangledURIList += uri;\r
+\r
+                       } else {\r
+                               // すでに登録済みであれば、再度、そのインデックスを使用する.\r
+                               if (pos > 0) {\r
+                                       adjustedMangledName += "_" + pos;\r
+                               }\r
+                       }\r
+                       \r
+                       // 登録\r
+                       mangledProps.put(MANGLED_NAME_PREFIX + adjustedMangledName, uri); // 補正後UUIDからURIへの索引\r
+                       mangledProps.put(URI_KEY_PREFIX + uri, adjustedMangledName); // URIから補正語UUIDへの索引\r
+                       mangledProps.put(mangledLookupKey, sameMangledURIList); // 補正前UUIDを使用するURIリスト\r
+\r
+               } else {\r
+                       // 登録済みの場合\r
+                       adjustedMangledName = registeredMangledName;\r
+               }\r
+               \r
+               return adjustedMangledName;\r
+       }\r
+\r
+       /**\r
+        * DocBaseのURIを辞書に登録し、そのハッシュ化文字列を返す.<br>\r
+        * ハッシュの衝突を回避するための辞書ファイル「mangled_info.xml」を使用して、\r
+        * 衝突した場合は末尾に連番をふることで補正を行う.<br>\r
+        * @param docBase URI\r
+        * @param storeDir 格納先ディレクトリ(格納先単位で辞書ファイルが作成される)\r
+        * @return ハッシュ化文字列(衝突した場合は連番が振られる)\r
+        */\r
+       protected String registerMangledName(URI docBase, File storeDir) {\r
+               final String noAdjustedMangledName = getNoAdjustedMangledName(docBase);\r
+               String adjustedMangledName = noAdjustedMangledName;\r
+               FileMappedProperties mangledProps = null;\r
+               try {\r
+                       mangledProps = openMetaFile(storeDir);\r
+                       try {\r
+                               // 名前を登録する.\r
+                               adjustedMangledName = registerMangledName(docBase, mangledProps);\r
+       \r
+                               // 追加・変更されていたら保存する.\r
+                               if (mangledProps.isModified()) {\r
+                                       mangledProps.save();\r
+                               }\r
+\r
+                       } finally {\r
+                               mangledProps.close();\r
+                       }\r
+\r
+               } catch (Exception ex) {\r
+                       logger.log(\r
+                                       Level.WARNING,\r
+                                       "mangled database is broken." + ((mangledProps == null) ? storeDir\r
+                                                       : mangledProps.getFile()), ex);\r
+               }\r
+               \r
+               return adjustedMangledName;\r
+       }\r
+\r
+       /**\r
+        * 登録されている、すべてのハッシュ化文字列に対するURIを取得する.<br>\r
+        * @param mangledNames ハッシュ化文字列のコレクション\r
+        * @param storeDir 格納先ディレクトリ(格納先単位で辞書ファイルが作成される)\r
+        * @param ope ハッシュ化文字列に対応するURIが発見された場合のオペレーション\r
+        */\r
+       public Map<String, URI> getMangledNameMap(File storeDir) {\r
+               if (storeDir == null) {\r
+                       throw new IllegalArgumentException();\r
+               }\r
+               \r
+               HashMap<String, URI> uris = new HashMap<String, URI>();\r
+               \r
+               FileMappedProperties mangledProps = null;\r
+               try {\r
+                       mangledProps = openMetaFile(storeDir);\r
+                       try {\r
+                               for (Map.Entry<String, String> propsEntry : mangledProps.entrySet()) {\r
+                                       String key = propsEntry.getKey();\r
+                                       String value = propsEntry.getValue();\r
+                                       if (key.startsWith("mangled.")) {\r
+                                               try {\r
+                                                       String mangledName = key.substring(8);\r
+                                                       URI uri = new URI(value);\r
+                                                       \r
+                                                       if (logger.isLoggable(Level.FINEST)) {\r
+                                                               logger.log(Level.FINEST, "registered mangled name: " + mangledName + "=" + uri);\r
+                                                       }\r
+                                                       uris.put(mangledName, uri);\r
+\r
+                                               } catch (Exception ex) {\r
+                                                       logger.log(Level.WARNING,\r
+                                                                       "mangled database is broken."\r
+                                                                                       + mangledProps.getFile(), ex);\r
+                                               }\r
+                                       }\r
+                               }\r
+\r
+                       } finally {\r
+                               mangledProps.close();\r
+                       }\r
+\r
+               } catch (Exception ex) {\r
+                       logger.log(\r
+                                       Level.WARNING,\r
+                                       "mangled database is broken." + ((mangledProps == null) ? storeDir\r
+                                                       : mangledProps.getFile()), ex);\r
+               }\r
+               \r
+               return uris;\r
+       }\r
+       \r
+       /**\r
+        * URIのコレクションを指定して、それぞれの登録済みのハッシュ化文字列をマップとして返す.<br>\r
+        * 登録フラグがfalseの場合、まだ登録されていないものはnullとなる.<br>\r
+        * そうでない場合は新規に登録され、その値が設定される.<br>\r
+        * @param uris URIのコレクション\r
+        * @param storeDir 格納先ディレクトリ(格納先単位で辞書ファイルが作成される)\r
+        * @param register 検索時に存在しなければ登録する場合はtrue\r
+        * @return URIをキーとし、ハッシュ化文字列を値とするマップ。登録されていないURIはnullが値となる.<br>\r
+        */\r
+       public Map<URI, String> getMangledNameMap(Collection<URI> uris, File storeDir, boolean register) {\r
+               if (storeDir == null) {\r
+                       throw new IllegalArgumentException();\r
+               }\r
+               if (uris == null || uris.isEmpty()) {\r
+                       // nullまたは空の場合は空を返す.\r
+                       return Collections.emptyMap();\r
+               }\r
+               \r
+               HashMap<URI, String> results = new HashMap<URI, String>();\r
+               \r
+               FileMappedProperties mangledProps = null;\r
+               try {\r
+                       mangledProps = openMetaFile(storeDir);\r
+                       try {\r
+                               for (URI uri : uris) {\r
+                                       if (uri == null) {\r
+                                               continue; // 不正uri\r
+                                       }\r
+\r
+                                       String mangledName = mangledProps.get(uri.toASCIIString());\r
+                                       if (mangledName == null) {\r
+                                               // 未登録の場合\r
+                                               if (register && uri != null) {\r
+                                                       // 登録する場合\r
+                                                       mangledName = registerMangledName(uri, mangledProps);\r
+                                               }\r
+                                       }\r
+                                       results.put(uri, mangledName);\r
+                               }\r
+\r
+                               // 登録する場合で変更があれば保存する.\r
+                               if (register && mangledProps.isModified()) {\r
+                                       mangledProps.save();\r
+                               }\r
+                               \r
+                       } finally {\r
+                               mangledProps.close();\r
+                       }\r
+               } catch (Exception ex) {\r
+                       logger.log(\r
+                                       Level.WARNING,\r
+                                       "mangled database is broken." + ((mangledProps == null) ? storeDir\r
+                                                       : mangledProps.getFile()), ex);\r
+               }\r
+               return results;\r
        }\r
 \r
        /**\r
-        * docBaseã\81\94ã\81¨ã\81«ã\81®ã\83\8fã\83\83ã\82·ã\83¥å\80¤ã\82\92æ\96\87å­\97å\88\97表ç\8f¾å\8c\96ã\81\97ã\81\9fã\83\97ã\83¬ã\83\95ã\82£ã\83\83ã\82¯ã\82¹を返す.<br>\r
-        * docBaseã\81\8cnullã\81®å ´å\90\88ã\81¯ç©ºæ\96\87å­\97ã\81¨ã\81¿ã\81ªã\81\99.<br>\r
+        * docBaseã\82\92ã\83\8fã\83\83ã\82·ã\83¥å\80¤å\8c\96æ\96\87å­\97å\88\97ã\81«ã\81\97ã\81\9fã\80\81è£\9cæ­£å\89\8dã\81®æ\96\87å­\97å\88\97を返す.<br>\r
+        * docBaseã\81\8cnullã\81®å ´å\90\88ã\81¯ç©ºæ\96\87å­\97ã\81¨ã\81¿ã\81ªã\81\97ã\81¦å¤\89æ\8f\9bã\81\99ã\82\8b.<br>\r
         * @param docBase URI、null可\r
         * @return ハッシュ値の文字列表現\r
         */\r
-       public String getMangledNamedPrefix(URI docBase) {\r
+       private String getNoAdjustedMangledName(URI docBase) {\r
                String docBaseStr;\r
                if (docBase == null) {\r
                        docBaseStr = "";\r
                } else {\r
                        docBaseStr = docBase.toString();\r
                }\r
-               String prefix = UUID.nameUUIDFromBytes(docBaseStr.getBytes()).toString();\r
-               return prefix;\r
+               String mangledName = UUID.nameUUIDFromBytes(docBaseStr.getBytes()).toString();\r
+\r
+               if (logger.isLoggable(Level.FINEST)) {\r
+                       logger.log(Level.FINEST, "mangledName " + docBase + "=" + mangledName);\r
+               }\r
+               \r
+               return mangledName;\r
        }\r
 }\r
 \r