<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
<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
--- /dev/null
+<!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
--- /dev/null
+<!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
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
// なによりも、まず、これを判定しないとダメ.(順序が重要)\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
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
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
\r
CharacterDataPersistent persist = CharacterDataPersistent.getInstance();\r
\r
- // character.xmlとして妥当な文書であるか検査する.\r
+ // favorites.xmlとして妥当な文書であるか検査する.\r
DocInfo docInfo;\r
InputStream is = favoritesXml.openStream();\r
try {\r
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
});\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
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
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
\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
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
import charactermanaj.util.DirectoryConfig;\r
import charactermanaj.util.UserDataFactory;\r
\r
+\r
/**\r
* 開始前の事前準備するためのサポートクラス \r
* @author seraphy\r
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
*/\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
\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
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
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
\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
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
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
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
}\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
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
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
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
\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
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
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
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
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
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
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
loadCharacterData(characterData);\r
loadFavorites(characterData);\r
\r
- } catch (IOException ex) {\r
+ } catch (Exception ex) {\r
ErrorMessageHelper.showErrorDialog(null, ex);\r
characterData = null;\r
}\r
\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
--- /dev/null
+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
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
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
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
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
}\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
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
\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
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
\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
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
}\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
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
--- /dev/null
+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
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
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
/**\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