<build>
<plugins>
<plugin>
- <!-- Launch4jによるjarファイルのexe化を行う. http://launch4j.sourceforge.net/docs.html -->
- <groupId>org.bluestemsoftware.open.maven.plugin</groupId>
- <artifactId>launch4j-plugin</artifactId>
- <version>1.5.0.0</version>
- <executions>
+ <!-- Launch4jによるjarファイルのexe化を行う. http://launch4j.sourceforge.net/docs.html
+ プラグインが1.7.24の場合、使用するのはLaunch4j 3.12 である。
+ https://github.com/lukaszlenart/launch4j-maven-plugin/blob/master/pom.xml -->
+ <groupId>com.akathist.maven.plugins.launch4j</groupId>
+ <artifactId>launch4j-maven-plugin</artifactId>
+ <version>1.7.24</version>
+ <executions>
<execution>
<id>l4j-gui</id>
<phase>package</phase>
-[[リリースノート (キャラクターなんとかJ - 0.998)]]
-2015/07/20
+[[リリースノート (キャラクターなんとかJ - 0.999)]]
+2015/12/07
ホームページ
http://osdn.jp/projects/charactermanaj/
-[ver0.997からの変更点]
+[ver0.998からの変更点]
-1. 頂き物の中国語リソース(zh)を本体にマージしました。
-2. フォントの選択で表示できない文字がある場合は除外するようにしました。
+(機能的変更点)
+・お気に入りに登録した場合、アクセサリ等同一のカテゴリに複数のアイテムがある場合の順序がでたらめになっていた問題を修正しました。
+・カスタムレイヤーパターンによる、レイヤー順序の変更に対応しました。
+ ・ これは既定のレイヤー順序を一時的に変更するもので、既存の構造に割り込めるように小数点での指定が可能になっています。
+ ・ デフォルトのキャラクターデータ構造(v3)の場合、自動的に「目を前髪で隠す」設定が追加されます。
+・アプリケーション設定で「ウィンドウサイズ、位置、ズームの復元」をtrueにすると、起動時に前回終了時のズーム状態やウィンドウサイズを復元します。
+・情報ウィンドウをモードレスにし、現在の表示中の画像の重ね順を把握できるように修正しました。
+・ログ、ワーキング状態などのファイルを%APPDATA%フォルダから%LOCALAPPDATA%フォルダに変更しました。
-※ Ver0.997で優先フォントの指定をできるように修正したのですが、日本語フォントを優先的に使用させるため、メイリオ等があれば、それを選択するようにしていました。
-しかし、頂いた中国語リソースを使う場合、メイリオがある場合には中国語を使用しつつメイリオで表示させることになるため、中国語文字が文字化けしてしまう問題がありました。
-現在のロケールにあわせた文字列リソースを表示できないフォントは選択から除外するようにしました。
+(内部的変更点)
+・Java11でも実行できるように修正しています。Java6以降で実行できます。(Java6互換でビルドしているためJava5では実行できなくなりました。)
+ ・Windowsの場合、exe形式をサポートするLaunch4jもJava9以降に対応している3系に変更しています。
+・内部的にシリアライズ形式の読み書きは全廃しました。(ver0.997の時点でシリアライズで保存しておらず、ver0.999でソースからも消しました。)
+・古いワーキング状態で対象となるデータセットが存在しなくなっている場合はゴミとみなして削除するようにしました。
+・フォント除外判定をCodePointで行うように修正しました。
+
+[その他]
+
+Windows版ではJava8まではHiDPI環境に対応していないため、Surface Proなどの高解像度モニタでは画面が小さすぎるかもしれません。
+Java11では画面の設定の倍率で拡大して表示されるため、Java11で試してみるのもよいかもしれません。
+(アプリケーション的には何も対応していないので、表示される画像も単純に拡大されたものになります。)
[インストール方法]
以下の環境での想定を行っております。
Windows 7(32/64)
-Windows 8.1(32/64)
+Windows 10(32/64)
+
+可能であればJava8での利用をおすすめします。
+Java11でも利用可能です。
+Java6でコンパイルしているため、Java6のデスクトップをサポートする環境であれば基本的には動作すると思います。
-可能であればJava7またはJava8での利用をおすすめします。
-JavaSE5で作成されているため、JavaSE5のデスクトップをサポートする環境であれば基本的には動作すると思います。
[使用・作成されるファイル等について]
-[[リリースノート (キャラクターなんとかJ - 0.998 - JRE8同梱版)]]
-2015/07/20
+[[リリースノート (キャラクターなんとかJ - 0.999 - JRE8同梱版)]]
+2018/12/07
ホームページ
http://osdn.jp/projects/charactermanaj/
-[ver0.997からの変更点]
-
-1. 頂き物の中国語リソース(zh)を本体にマージしました。
-2. フォントの選択で表示できない文字がある場合は除外するようにしました。
-
-※ Ver0.997で優先フォントの指定をできるように修正したのですが、日本語フォントを優先的に使用させるため、メイリオ等があれば、それを選択するようにしていました。
-しかし、頂いた中国語リソースを使う場合、メイリオがある場合には中国語を使用しつつメイリオで表示させることになるため、中国語文字が文字化けしてしまう問題がありました。
-現在のロケールにあわせた文字列リソースを表示できないフォントは選択から除外するようにしました。
+[ver0.998からの変更点]
+・同梱するJavaランタイム(JRE)は AdoptOpenJDK 8u192-b12 にしています。
+
+(機能的変更点)
+・お気に入りに登録した場合、アクセサリ等同一のカテゴリに複数のアイテムがある場合の順序がでたらめになっていた問題を修正しました。
+・カスタムレイヤーパターンによる、レイヤー順序の変更に対応しました。
+ ・ これは既定のレイヤー順序を一時的に変更するもので、既存の構造に割り込めるように小数点での指定が可能になっています。
+ ・ デフォルトのキャラクターデータ構造(v3)の場合、自動的に「目を前髪で隠す」設定が追加されます。
+・アプリケーション設定で「ウィンドウサイズ、位置、ズームの復元」をtrueにすると、起動時に前回終了時のズーム状態やウィンドウサイズを復元します。
+・情報ウィンドウをモードレスにし、現在の表示中の画像の重ね順を把握できるように修正しました。
+・ログ、ワーキング状態などのファイルを%APPDATA%フォルダから%LOCALAPPDATA%フォルダに変更しました。
+ ・ 既存のログファイル等は自動的には削除しないので不要であれば消してください。
+ ・ 初回起動時に前回の作業状態が無くなっている状態になるのでご注意ください。(キャラクターデータ等はそのままです。)
+
+(内部的変更点)
+・Java11でも実行できるように修正しています。Java6以降で実行できます。(Java6互換でビルドしているためJava5では実行できなくなりました。)
+ ・Windowsの場合、exe形式をサポートするLaunch4jもJava9以降に対応している3系に変更しています。
+・内部的にシリアライズ形式の読み書きは全廃しました。(ver0.997の時点でシリアライズで保存しておらず、ver0.999でソースからも消しました。)
+・古いワーキング状態で対象となるデータセットが存在しなくなっている場合はゴミとみなして削除するようにしました。
+・フォント除外判定をCodePointで行うように修正しました。
+
+[その他]
+
+Windows版ではJava8まではHiDPI環境に対応していないため、Surface Proなどの高解像度モニタでは画面が小さすぎるかもしれません。
+Java11では画面の設定の倍率で拡大して表示されるため、Java11で試してみるのもよいかもしれません。
+(アプリケーション的には何も対応していないので、表示される画像も単純に拡大されたものになります。)
[インストール方法]
-CharacterManaJ_0.998_with_JRE8を好きなフォルダに展開して実行するだけです。
+CharacterManaJ_0.999_with_JRE8を好きなフォルダに展開して実行するだけです。
特にインストール作業は必要ありません。
-OracleのJavaランタイムがアプリケーションに同梱されています。
+AdoptOpenJDK 8u192-b12のJavaランタイムがアプリケーションに同梱されています。
+https://adoptopenjdk.net/
+
マシンにJavaをインストールしていない場合でも、Javaのインストールなしに実行できます。
※ 本アプリケーションには画像データは含まれていません。
以下の環境での想定を行っております。
Windows 7(32/64)
-Windows 8.1(32/64)
+Windows 10(32/64)
-同梱されているJREは、Java8(jdk1.8.0_51 32ビット版)となります。
+AdoptOpenJDK 8u192-b12のJavaランタイムがアプリケーションに同梱されています。
より新しいJavaを利用したい場合は、jreフォルダの中身を新しいjreのものとまるごと差し替えてください。
-また、jreフォルダを削除するとシステムにインストールされているJavaを利用するようになります。
+Java11での利用も可能です。
+また、jreフォルダを削除するとシステムにインストールされているJavaを利用するようになります。(レジストリに登録されていれば)
[使用・作成されるファイル等について]
-キャラクターなんとかJ Ver0.998
-2015/07/20
+キャラクターなんとかJ Ver0.999
+2018/12/07
ホームページ
http://osdn.jp/projects/charactermanaj/
-[ver0.997からの変更点]
+[ver0.998からの変更点]
-1. 頂き物の中国語リソース(zh)を本体にマージしました。
-2. フォントの選択で表示できない文字がある場合は除外するようにしました。
+・JRE同梱版のJavaランタイム(JRE)は AdoptOpenJDK 8u192-b12 にしています。
-※ Ver0.997で優先フォントの指定をできるように修正したのですが、日本語フォントを優先的に使用させるため、メイリオ等があれば、それを選択するようにしていました。
-しかし、頂いた中国語リソースを使う場合、メイリオがある場合には中国語を使用しつつメイリオで表示させることになるため、中国語文字が文字化けしてしまう問題がありました。
-現在のロケールにあわせた文字列リソースを表示できないフォントは選択から除外するようにしました。
+(機能的変更点)
+・お気に入りに登録した場合、アクセサリ等同一のカテゴリに複数のアイテムがある場合の順序がでたらめになっていた問題を修正しました。
+・カスタムレイヤーパターンによる、レイヤー順序の変更に対応しました。
+ ・ これは既定のレイヤー順序を一時的に変更するもので、既存の構造に割り込めるように小数点での指定が可能になっています。
+ ・ デフォルトのキャラクターデータ構造(v3)の場合、自動的に「目を前髪で隠す」設定が追加されます。
+・アプリケーション設定で「ウィンドウサイズ、位置、ズームの復元」をtrueにすると、起動時に前回終了時のズーム状態やウィンドウサイズを復元します。
+・情報ウィンドウをモードレスにし、現在の表示中の画像の重ね順を把握できるように修正しました。
-[ファイルの説明]
-
-* キャラクターなんとかJ.app
- OracleのJava7またはJava8を使用するバージョンです。
- あらかじめ、OracleのサイトよりJava7またはJava8をインストールしてください。
- 事前にOracleのJava7以降をインストールしていない場合は起動時にエラーとなります。
+(内部的変更点)
+・Java11でも実行できるように修正しています。Java6以降で実行できます。(Java6互換でビルドしているためJava5では実行できなくなりました。)
+・内部的にシリアライズ形式の読み書きは全廃しました。(ver0.997の時点でシリアライズで保存しておらず、ver0.999でソースからも消しました。)
+・古いワーキング状態で対象となるデータセットが存在しなくなっている場合はゴミとみなして削除するようにしました。
+・フォント除外判定をCodePointで行うように修正しました。
- ※ JRE同梱版の場合は、Javaがアプリケーションに含まれていますので、Javaのインストール作業は不要です。
+[ファイルの説明]
-* java6/キャラクターなんとかJ.app
- AppleのMac OS X用のJava6を使用するバージョンです。
- 起動時に、まだJavaがインストールされていない場合は自動インストールが開始されます。
- ただし、OSX10.9 Marvericks では、Javaの自動インストールがうまく行かないケースがあるようです。
- その場合は、手動でAppleもしくはOracleのサイトより、Javaランタイムをインストールしてください。
-
- ※ JRE同梱版には含まれていません。
+* キャラクターなんとかJ.app
+ アプリケーション本体です。好きなフォルダにコピーしてください。
+ JRE同梱版の場合は、Javaがアプリケーションに含まれていますので、Javaのインストール作業は不要です。
+ ※ バンドルファイルを展開すると Contents/Plugins/JRE というフォルダがあり、ここにあるJavaを利用します。
+ 異なるバージョンのJavaを使いたい場合は、このフォルダを差し替えてください。
+ (また、このJREフォルダを消すと、非JRE同梱版と同じになります。)
-* CharacterManaJ.jar
- 実行可能jarです。Java6,7,8のいずれかがインストールされていればダブルクリックで実行できます。
- ただし、Java7, 8の場合はJava7u60以降、Java8u5以降の最新のものをお使いください。
- Mac用の設定ファイルがないため、上記appとは、かならずしも同じではありません。
- よくJavaの特性を知っている方向けです。
+ JRE同梱版でない場合、環境変数JAVA_HOME、もしくはシステムにインストールされているjavaが検索して使用されます。
+ Ver0.999よりランチャーはシェルスクリプトになっており JAVA_HOMEまたは/usr/libexec/java_homeでjavaを検索します。
+ ※ 環境変数JAVA_HOMEが設定されていない場合は、/usr/libexec/java_home で示されるものをJAVA_HOMEとして使用します。
- ※ JRE同梱版には含まれていません。
+ Ver0.999より、~/Library/CharacterManaJ/jvm_options というテキストファイルに起動オプションを指定できようになりました。
不明な点があればプロジェクトのWikiを参照してください。
}
}
+ // スタートアップ時の初期化
+ // ver0.999ではキャラクターデータディレクトリに依存しない初期化部しかないので最初に移動する。
+ // (APPDATAからLOCALAPPDATAへの移動処理などがあるため、先に行う必要がある。)
+ StartupSupport.getInstance().doStartup();
+
// 起動時のシステムプロパティでキャラクターディレクトリが指定されていて実在すれば、それを優先する.
File currentCharacterDir = null;
String charactersDir = System.getProperty("charactersDir");
// キャラクターデータフォルダの設定
DirectoryConfig.getInstance().setCharactersDir(currentCharacterDir);
- // スタートアップ時の初期化
- StartupSupport.getInstance().doStartup();
-
// デフォルトのプロファイルを開く.
// (最後に使ったプロファイルがあれば、それが開かれる.)
final MainFrame mainFrame = ProfileListManager.openDefaultProfile();
/**
* 最後に使用したデータの使用状況を保存・復元するためのクラス
- *
+ *
* @author seraphy
*/
public final class RecentDataPersistent {
private RecentDataPersistent() {
super();
}
-
+
/**
* インスタンスを取得する
- *
+ *
* @return インスタンス
*/
public static RecentDataPersistent getInstance() {
/**
* キャラクターデータの親フォルダごとに保存する、最後に使用したキャラクターデータを保存するファイル
- *
+ *
* @return
*/
private File getRecentCharacterXML() {
/**
* 最後に使用したキャラクターデータのフォルダ名を親ディレクトリからの相対パスとして保存する.<br>
* ただし、書き込み禁止である場合は何もしない.<br>
- *
+ *
* @param characterData
* キャラクターデータ
* @throws IOException
* 親ディレクトリからの相対パスとして記録されている、最後に使用したキャラクターデータのフォルダ名から、
* 最後に使用したキャラクターデータをロードして返す.<br>
* 該当するキャラクターデータが存在しないか、読み込みに失敗した場合は「履歴なし」としてnullを返す.<br>
- *
+ *
* @return キャラクターデータ、もしくはnull
*/
public CharacterData loadRecent() {
// 履歴がない場合、もしくは読み取れなかった場合はnullを返す.
return null;
}
-
- // /**
- // * 最後に使用したキャラクターデータを取得する.
- // *
- // * @return キャラクターデータ。最後に使用したデータが存在しない場合はnull
- // * @throws IOException
- // * 読み込みに失敗した場合
- // */
- // private CharacterData loadRecentSer() throws IOException {
- // UserData recentCharacterStore = getRecentCharacterStore();
- // if (!recentCharacterStore.exists()) {
- // return null;
- // }
- //
- // RecentData recentData;
- // try {
- // File currentCharactersDir =
- // DirectoryConfig.getInstance().getCharactersDir();
- //
- // Object rawRecentData = recentCharacterStore.load();
- // if (rawRecentData instanceof RecentData) {
- // // 旧形式 (単一)
- // recentData = (RecentData) rawRecentData;
- // logger.log(Level.INFO, "old-recentdata-type: " + recentData);
- //
- // // 旧形式で保存されているURIが、現在選択しているキャラクターデータと同じ親ディレクトリ
- // // でなければ復元しない.
- // URI uri = recentData.getDocBase();
- // File parentDir = new File(uri).getParentFile().getParentFile(); // 2段上
- // if (!currentCharactersDir.equals(parentDir)) {
- // logger.log(Level.INFO,
- // "unmatched characters-dir. current="
- // + currentCharactersDir + "/recent="
- // + parentDir);
- // recentData = null;
- // }
- //
- // } else if (rawRecentData instanceof Map) {
- // // 新形式 (複数のキャラクターディレクトリに対応)
- // @SuppressWarnings("unchecked")
- // Map<File, RecentData> recentDataMap = (Map<File, RecentData>)
- // rawRecentData;
- // recentData = recentDataMap.get(currentCharactersDir);
- // logger.log(Level.FINE, "recent-data: " + currentCharactersDir + "=" +
- // recentData);
- //
- // } else {
- // // 不明な形式
- // logger.log(Level.SEVERE,
- // "invalid file format. " + recentCharacterStore
- // + "/class=" + rawRecentData.getClass());
- // recentData = null;
- // }
- //
- // } catch (Exception ex) {
- // // RecentData情報の復元に失敗した場合は最後に使用したデータが存在しないものとみなす.
- // logger.log(Level.WARNING, "recent data loading failed. " +
- // recentCharacterStore, ex);
- // recentData = null;
- // }
- //
- // if (recentData != null) {
- // CharacterDataPersistent persist = CharacterDataPersistent.getInstance();
- // return persist.loadProfile(recentData.getDocBase());
- // }
- //
- // // 履歴がない場合、もしくは読み取れなかった場合はnullを返す.
- // return null;
- // }
- //
- // /**
- // * 最後に使用したキャラクタデータの保存先を取得する
- // *
- // * @return 保存先
- // */
- // protected UserData getRecentCharacterStore() {
- // UserDataFactory userDataFactory = UserDataFactory.getInstance();
- // UserData recentCharacterStore =
- // userDataFactory.getUserData(RECENT_CHARACTER_SER);
- // return recentCharacterStore;
- // }
-
-
}
package charactermanaj.model.io;
+import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileFilter;
+import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* ワーキングセットのサフィックス.
*/
- public static final String WORKINGSET_FILE_SUFFIX = "workingset.xml";
+ private static final String WORKINGSET_FILE_SUFFIX = "workingset.xml";
private static final WorkingSetPersist singletion = new WorkingSetPersist();
+ private final UserDataFactory userDataFactory = UserDataFactory.getLocalInstance();
+
public static WorkingSetPersist getInstance() {
return singletion;
}
* すべてのワーキングセットをクリアする.<br>
*/
public void removeAllWorkingSet() {
- UserDataFactory userDataFactory = UserDataFactory.getInstance();
- File dir = userDataFactory.getSpecialDataDir("foo-"
- + WORKINGSET_FILE_SUFFIX);
+ File dir = userDataFactory.getSpecialDataDir("foo-" + WORKINGSET_FILE_SUFFIX);
if (dir.exists() && dir.isDirectory()) {
File[] files = dir.listFiles(new FileFilter() {
public boolean accept(File pathname) {
- return pathname.isFile()
- && pathname.getName().endsWith(
- WORKINGSET_FILE_SUFFIX);
+ return pathname.isFile() && pathname.getName().endsWith(WORKINGSET_FILE_SUFFIX);
}
});
if (files == null) {
* 対象のキャラクターデータ
*/
public void removeWorkingSet(CharacterData cd) {
- UserDataFactory userDataFactory = UserDataFactory.getInstance();
UserData workingSetXmlData = userDataFactory.getMangledNamedUserData(
cd.getDocBase(), WORKINGSET_FILE_SUFFIX);
if (workingSetXmlData != null && workingSetXmlData.exists()) {
}
// XML形式でのワーキングセットの保存
- UserDataFactory userDataFactory = UserDataFactory.getInstance();
UserData workingSetXmlData = userDataFactory.getMangledNamedUserData(
characterData.getDocBase(), WORKINGSET_FILE_SUFFIX);
OutputStream outstm = workingSetXmlData.getOutputStream();
throw new IllegalArgumentException();
}
// XML形式でのワーキングセットの復元
- UserDataFactory userDataFactory = UserDataFactory.getInstance();
UserData workingSetXmlData = userDataFactory.getMangledNamedUserData(
characterData.getDocBase(), WORKINGSET_FILE_SUFFIX);
if (workingSetXmlData == null || !workingSetXmlData.exists()) {
return ws;
}
+
+ /**
+ * 古いワーキングセットについて、すでに本体データが削除されている場合はワーキングセットも削除する。
+ * まだ存在するものは削除されない。
+ * 生きている場合は、ワーキングセットの検査日時を表すために更新日時を現在日時に設定しなおす。
+ * @param expireDate 判定対象となる日時、それ以前のもののみ判定を行う。
+ */
+ public void purge(final long expireDate) {
+ final String XML_SUFFIX = "-" + WORKINGSET_FILE_SUFFIX;
+
+ UserDataFactory userDataFactory = UserDataFactory.getLocalInstance();
+ File dir = userDataFactory.getSpecialDataDir(XML_SUFFIX);
+
+ File[] xmls = dir.listFiles(new FileFilter() {
+ @Override
+ public boolean accept(File pathname) {
+ return pathname.isFile() && pathname.getName().endsWith(XML_SUFFIX)
+ && pathname.lastModified() < expireDate && pathname.length() > 0;
+ }
+ });
+ if (xmls == null) {
+ logger.log(Level.WARNING, "workingset-dir access failed.");
+ return;
+ }
+
+ for (File xmlFile : xmls) {
+ try {
+ WorkingSetXMLReader reader = new WorkingSetXMLReader();
+ IndependentWorkingSet ws;
+ InputStream is = new BufferedInputStream(new FileInputStream(xmlFile));
+ try {
+ ws = reader.loadWorkingSet(is);
+ } finally {
+ is.close();
+ }
+
+ URI docBase = ws.getCharacterDocBase();
+ if (docBase.getScheme().equals("file")) {
+ File characterXml = new File(docBase);
+ if (!characterXml.exists()) {
+ // キャラクター定義XMLが存在しない = 削除されたキャラクターデータ
+ logger.log(Level.INFO, "remove amandone workingset: " + xmlFile + ", docBase=" + docBase);
+ xmlFile.delete();
+ } else {
+ // チェック済みであることを示すためにXMLの更新日時を現在時刻にする
+ xmlFile.setLastModified(System.currentTimeMillis());
+ }
+ }
+
+ } catch (Exception ex) {
+ logger.log(Level.WARNING, "file access failed. " + xmlFile, ex);
+ }
+ }
+ }
}
package charactermanaj.model.util;
-import java.io.BufferedInputStream;
import java.io.File;
-import java.io.FileFilter;
-import java.io.FileInputStream;
-import java.io.InputStream;
-import java.net.URI;
import java.util.logging.Level;
import java.util.logging.Logger;
import charactermanaj.model.AppConfig;
-import charactermanaj.model.IndependentWorkingSet;
-import charactermanaj.model.io.WorkingSetXMLReader;
-import charactermanaj.util.UserDataFactory;
+import charactermanaj.model.io.WorkingSetPersist;
+import charactermanaj.util.ApplicationLogHandler;
+import charactermanaj.util.ConfigurationDirUtilities;
/**
@Override
public void doStartup() {
StartupSupport[] startups = {
+ new MoveAppDataToLocalAppData(),
new PurgeOldLogs(),
new PurgeOldWorkingSetXml(),
//new ConvertRecentCharDirsSerToXmlProps(),
}
/**
- * 使われていないWorkingset.xmlを削除する
+ * APPDATAフォルダにあったワーキングセット類をLOCALAPPDATAに移動する。
*/
-class PurgeOldWorkingSetXml extends StartupSupport {
+class MoveAppDataToLocalAppData extends StartupSupport {
+ /**
+ * ロガー
+ */
private final Logger logger = Logger.getLogger(getClass().getName());
@Override
public void doStartup() {
+ File appDataDir = ConfigurationDirUtilities.getUserDataDir();
+ File localAppDataDir = ConfigurationDirUtilities.getLocalUserDataDir();
+ if (appDataDir.equals(localAppDataDir)) {
+ // 移動元・移動先が同一であれば何もしない。
+ return;
+ }
+
+ // ログフォルダ、ログ設定はすでにログが開始されているので移動しない。
+ String[] files = {"recent-characterdirs.xml", "workingset"};
+ for (String file : files) {
+ try {
+ File target = new File(localAppDataDir, file);
+ if (target.exists()) {
+ // すでにあれば何もしない
+ continue;
+ }
+ File source = new File(appDataDir, file);
+ if (source.exists()) {
+ // まだ移動されていない場合であれば移動を試みる
+ boolean result = source.renameTo(target);
+ logger.log(Level.INFO, "Move " + source + " to " + target +
+ ". result=" + (result ? "succeeded" : "failed"));
+ }
+ } catch (Exception ex) {
+ logger.log(Level.SEVERE, "Failed to move file. " + file, ex);
+ }
+ }
+ }
+}
+
+/**
+ * 使われていないWorkingset.xmlを削除する
+ */
+class PurgeOldWorkingSetXml extends StartupSupport {
+
+ @Override
+ public void doStartup() {
AppConfig appConfig = AppConfig.getInstance();
long purgeOldLogsMillSec = appConfig.getPurgeLogDays() * 24L * 3600L * 1000L;
if (purgeOldLogsMillSec <= 0) {
return;
}
- final String XML_SUFFIX = "-workingset.xml";
-
- UserDataFactory userDataFactory = UserDataFactory.getInstance();
- File dir = userDataFactory.getSpecialDataDir(XML_SUFFIX);
-
// ワーキングセットの掃除判定日時
// これよりも新しいものは実際に使われているかを問わず、削除判定しない。
final long expireDate = System.currentTimeMillis() - purgeOldLogsMillSec;
- File[] xmls = dir.listFiles(new FileFilter() {
- @Override
- public boolean accept(File pathname) {
- return pathname.isFile() && pathname.getName().endsWith(XML_SUFFIX)
- && pathname.lastModified() < expireDate && pathname.length() > 0;
- }
- });
- if (xmls == null) {
- logger.log(Level.WARNING, "workingset-dir access failed.");
- return;
- }
-
- for (File xmlFile : xmls) {
- try {
- WorkingSetXMLReader reader = new WorkingSetXMLReader();
- IndependentWorkingSet ws;
- InputStream is = new BufferedInputStream(new FileInputStream(xmlFile));
- try {
- ws = reader.loadWorkingSet(is);
- } finally {
- is.close();
- }
-
- URI docBase = ws.getCharacterDocBase();
- if (docBase.getScheme().equals("file")) {
- File characterXml = new File(docBase);
- if (!characterXml.exists()) {
- // キャラクター定義XMLが存在しない = 削除されたキャラクターデータ
- logger.log(Level.INFO, "remove amandone workingset: " + xmlFile + ", docBase=" + docBase);
- xmlFile.delete();
- } else {
- // チェック済みであることを示すためにXMLの更新日時を現在時刻にする
- xmlFile.setLastModified(System.currentTimeMillis());
- }
- }
-
- } catch (Exception ex) {
- logger.log(Level.WARNING, "file access failed. " + xmlFile, ex);
- }
- }
+ // 指定した日時以前のワーキングセットについて本体データが削除されているものは
+ // ワーキングセットも削除する。
+ WorkingSetPersist persist = new WorkingSetPersist();
+ persist.purge(expireDate);
}
}
*/
class PurgeOldLogs extends StartupSupport {
- /**
- * ロガー
- */
- private final Logger logger = Logger.getLogger(getClass().getName());
-
@Override
public void doStartup() {
- UserDataFactory userDataFactory = UserDataFactory.getInstance();
- File logsDir = userDataFactory.getSpecialDataDir("*.log");
- if (logsDir.exists()) {
- AppConfig appConfig = AppConfig.getInstance();
- long purgeOldLogsMillSec = appConfig.getPurgeLogDays() * 24L * 3600L * 1000L;
- if (purgeOldLogsMillSec > 0) {
- File[] files = logsDir.listFiles();
- if (files == null) {
- logger.log(Level.WARNING, "log-dir access failed.");
- return;
- }
- long purgeThresold = System.currentTimeMillis()
- - purgeOldLogsMillSec;
- for (File file : files) {
- try {
- String name = file.getName();
- if (file.isFile() && file.canWrite()
- && name.endsWith(".log")) {
- long lastModified = file.lastModified();
- if (lastModified > 0
- && lastModified < purgeThresold) {
- boolean result = file.delete();
- logger.log(Level.INFO, "remove file " + file
- + "/succeeded=" + result);
- }
- }
-
- } catch (Exception ex) {
- logger.log(Level.WARNING,
- "remove file failed. " + file, ex);
- }
- }
- }
+ AppConfig appConfig = AppConfig.getInstance();
+ long purgeOldLogsMillSec = appConfig.getPurgeLogDays() * 24L * 3600L * 1000L;
+ if (purgeOldLogsMillSec > 0) {
+ // 期限切れのログファイルを削除する
+ long expiredDate = System.currentTimeMillis() - purgeOldLogsMillSec;
+ ApplicationLogHandler.purge(expiredDate);
}
}
}
Properties props = new Properties();
// ユーザーディレクトリのルート上に最後に使ったファイルリストをxml形式で保存する.
- File userDataDir = ConfigurationDirUtilities.getUserDataDir();
- File recentUseDirs = new File(userDataDir, RECENT_CHARACTERDIRS_XML);
+ File localUserDataDir = ConfigurationDirUtilities.getLocalUserDataDir();
+ File recentUseDirs = new File(localUserDataDir, RECENT_CHARACTERDIRS_XML);
if (recentUseDirs.exists()) {
InputStream is = new BufferedInputStream(new FileInputStream(
recentUseDirs));
&& lastUseCharacterDataDir.trim().length() > 0) {
lastUseCharacterDir = new File(new URI(
lastUseCharacterDataDir));
+ if (!lastUseCharacterDir.isDirectory()) {
+ // 存在しない場合
+ lastUseCharacterDir = null;
+ }
}
Enumeration<?> enmKeys = props.propertyNames();
if (key.startsWith(DIRS_PREFIX)) {
String value = props.getProperty(key);
if (value != null && value.trim().length() > 0) {
- dirsMap.put(key, new File(new URI(value)));
+ File dir = new File(new URI(value));
+ if (dir.isDirectory()) {
+ // 実在するフォルダのみ使用可能とする
+ dirsMap.put(key, dir);
+ }
}
}
}
props.put("doNotAskAgain", doNotAskAgain ? "true" : "false");
// ユーザーディレクトリのルート上に最後に使ったファイルリストをxml形式で保存する.
- File userDataDir = ConfigurationDirUtilities.getUserDataDir();
- File recentUseDirs = new File(userDataDir, RECENT_CHARACTERDIRS_XML);
+ File localUserDataDir = ConfigurationDirUtilities.getLocalUserDataDir();
+ File recentUseDirs = new File(localUserDataDir, RECENT_CHARACTERDIRS_XML);
OutputStream os = new BufferedOutputStream(new FileOutputStream(
recentUseDirs));
try {
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
+import java.util.logging.Logger;
import charactermanaj.model.AppConfig;
* このアプリケーションの活動を記録するログハンドラ.<br>
* アプリケーション用のディレクトリのlogsフォルダ下に開始日時のファイル名をもつログファイルを作成し、ログを記録する.<br>
* ただし、終了時、警告以上のログが一度も書き込まれなかった場合はログファィルは自動的に削除される.<br>
- *
+ *
* @author seraphy
*/
public class ApplicationLogHandler extends Handler {
private static final String LOGS_DIR = "logs";
-
+
private final Object lock = new Object();
-
+
private final File logFile;
-
+
private PrintWriter pw;
-
+
private boolean notRemove;
-
+
public ApplicationLogHandler() {
- File appDir = ConfigurationDirUtilities.getUserDataDir();
- File logsDir = new File(appDir, LOGS_DIR);
+ File localAppDir = ConfigurationDirUtilities.getLocalUserDataDir();
+ File logsDir = new File(localAppDir, LOGS_DIR);
if (!logsDir.exists()) {
if (!logsDir.mkdirs()) {
// ログ記録場所が作成できていないのでコンソールに出すしかない.
System.err.println("can't create the log directory. " + logsDir);
}
}
-
+
String fname = getCurrentTimeForFileName() + ".log";
logFile = new File(logsDir, fname);
PrintWriter tmp;
}
this.pw = tmp;
}
-
+
@Override
public void close() throws SecurityException {
synchronized (lock) {
- // 終了時にAppConfigにアクセスする.
+ // 終了時にAppConfigにアクセスする.
// (アプリケーションの終了時にアクセスすることで初期化タイミングの問題を避ける.)
try {
AppConfig appConfig = AppConfig.getInstance();
}
}
}
-
+
@Override
public void flush() {
synchronized (lock) {
}
}
}
-
+
@Override
public void publish(LogRecord record) {
if (record == null) {
String name = record.getLoggerName();
pw.println("#" + getCurrentTime() + " " + name + " "
+ lv.getLocalizedName() + " " + record.getMessage());
-
+
// 例外があれば、例外の記録
- Throwable tw = record.getThrown();
+ Throwable tw = record.getThrown();
if (tw != null) {
tw.printStackTrace(pw); // 例外のコールスタックをロガーに出力
}
-
+
// フラッシュする.(随時、ファイルの中身を見ることができるように.)
pw.flush();
}
}
}
-
+
public String getCurrentTime() {
Timestamp tm = new Timestamp(System.currentTimeMillis());
SimpleDateFormat dt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
SimpleDateFormat dt = new SimpleDateFormat("yyyy-MM-dd_HHmmssSSS");
return dt.format(tm);
}
+
+ /**
+ * ログフォルダ上にある、指定した期限以前のログファイルを削除する。
+ * @param expiredDate 期限切れとなる日時
+ */
+ public static void purge(long expiredDate) {
+ Logger logger = Logger.getLogger(ApplicationLogHandler.class.getName());
+
+ File localAppDir = ConfigurationDirUtilities.getLocalUserDataDir();
+ File logsDir = new File(localAppDir, LOGS_DIR);
+
+ File[] files = logsDir.listFiles();
+ if (files == null) {
+ logger.log(Level.WARNING, "log-dir access failed.");
+ return;
+ }
+ for (File file : files) {
+ try {
+ String name = file.getName();
+ if (file.isFile() && file.canWrite() && name.endsWith(".log")) {
+ long lastModified = file.lastModified();
+ if (lastModified > 0 && lastModified < expiredDate) {
+ boolean result = file.delete();
+ logger.log(Level.INFO, "remove file " + file + "/succeeded=" + result);
+ }
+ }
+
+ } catch (Exception ex) {
+ logger.log(Level.WARNING, "remove file failed. " + file, ex);
+ }
+ }
+ }
}
public final class ApplicationLoggerConfigurator {
private static final String LOGGING_PROPERTIES = "logging.properties";
-
+
private ApplicationLoggerConfigurator() {
super();
}
Exception configurationError = null;
try {
- // ユーザーごとのアプリケーション設定ディレクトリ上の設定ファイルを取得する.
- File appDataDir = ConfigurationDirUtilities.getUserDataDir();
- File logConfig = new File(appDataDir, LOGGING_PROPERTIES);
+ // ã\83¦ã\83¼ã\82¶ã\83¼ã\81\94ã\81¨ã\81®ã\83ã\83¼ã\82«ã\83«ã\82¢ã\83\97ã\83ªã\82±ã\83¼ã\82·ã\83§ã\83³è¨å®\9aã\83\87ã\82£ã\83¬ã\82¯ã\83\88ã\83ªä¸\8aã\81®è¨å®\9aã\83\95ã\82¡ã\82¤ã\83«ã\82\92å\8f\96å¾\97ã\81\99ã\82\8b.
+ File localAppDataDir = ConfigurationDirUtilities.getLocalUserDataDir();
+ File logConfig = new File(localAppDataDir, LOGGING_PROPERTIES);
if ( !logConfig.exists()) {
// ユーザ指定のロギングプロパティがない場合、リソースからコピーする
copyDefaultLogProperty(logConfig);
}
-
+
InputStream is = null;
if (logConfig.exists()) {
// ユーザー指定のロギングプロパティがある場合
// 処理は継続する.
configurationError = ex;
}
-
+
// ロガーを取得
Logger logger = Logger.getLogger(ApplicationLoggerConfigurator.class.getName());
-
+
// 初期化時に失敗した場合、デフォルトのコンソールハンドラを設定し、ログに出力する.
if (configurationError != null) {
logger.addHandler(new ConsoleHandler());
logger.addHandler(new ApplicationLogHandler());
logger.log(Level.WARNING, "LogConfigurationFailed", configurationError);
}
-
+
// 初期化時のログ
logger.info("open logger.");
logger.info("application configuration: baseDir="
+ ConfigurationDirUtilities.getApplicationBaseDir() + " appData="
+ ConfigurationDirUtilities.getUserDataDir());
}
-
+
/**
* デフォルトのログプロパティをユーザディレクトリにコピーする.
* @param logConfig ユーザディレクトリ上のログプロパティファイル位置
public static final String CONFIGURATION_DIR_NAME = "CharacterManaJ";
private static File userDataDir;
-
+
+ private static File localUserDataDir;
+
private static File applicationBaseDir;
-
+
private ConfigurationDirUtilities() {
throw new RuntimeException("utilities class.");
}
-
+
/**
* ユーザーごとのアプリケーションデータ保存先を取得する.<br>
* 環境変数「APPDATA」もしくはシステムプロパティ「appdata.dir」からベース位置を取得する.<br>
* Mac OS Xでなければ「~/」をベース位置とする.<br>
* これに対してシステムプロパティ「characterdata.dirname」(デフォルトは「CharacterManaJ」)という
* フォルダをユーザー毎のアプリケーションデータの保存先ディレクトリとする.<br>
+ *
+ * @return アプリケーションデータの保存先
*/
public synchronized static File getUserDataDir() {
if (userDataDir == null) {
-
+
String appData = null;
// システムプロパティ「appdata.dir」を探す
appData = System.getProperty("appdata.dir");
// ~/Libraryをベースにする.(Mac OS Xならば必ずある。)
appData = new File(System.getProperty("user.home"), "Library").getPath();
}
- if (appData == null) {
+ if (appData == null || appData.trim().length() == 0) {
// なければシステムプロパティ「user.home」を使う
// このプロパティは必ず存在する.
appData = System.getProperty("user.home");
}
- // システムプロパティ「characterdata.dirname」のディレクトリ名、なければ「CharacterManaJ」を設定する.
- String characterDirName = System.getProperty("characterdata.dirname", CONFIGURATION_DIR_NAME);
- userDataDir = new File(appData, characterDirName).getAbsoluteFile();
+ // ディレクトリを準備する
+ userDataDir = ensureAppDataDir(appData);
+ }
+ return userDataDir;
+ }
- // ディレクトリを準備する.
- if (!userDataDir.exists()) {
- if (!userDataDir.mkdirs()) {
- // ログ保管場所も設定されていないのでコンソールに出すしかない.
- System.err.println("can't create the user data directory. " + userDataDir);
- }
+ /**
+ * ユーザーごとのローカルアプリケーションデータ保存先を取得する.<br>
+ * システムプロパティlocal.appdata.dir、環境変数LOCALAPPDATAのいずれも設定されていない場合は
+ * {@link #getUserDataDir()}と同じである。
+ *
+ * @return ローカルなアプリケーションデータ保存先
+ */
+ public synchronized static File getLocalUserDataDir() {
+ if (userDataDir == null) {
+
+ String appData = null;
+ // システムプロパティ「local.appdata.dir」を探す
+ appData = System.getProperty("local.appdata.dir");
+ if (appData == null) {
+ // なければ環境変数LOCALAPPDATAを探す
+ // Windows2000/XP/Vista/Windows7には存在する.
+ appData = System.getenv("LOCALAPPDATA");
+ }
+
+ if (appData != null && appData.trim().length() > 0) {
+ localUserDataDir = ensureAppDataDir(appData);
+
+ } else {
+ // local.appdata.dirシステムプロパティ、LOCALAPPDATA環境変数、いずれもない場合は
+ // getUserDataDir()と同じものとする。
+ localUserDataDir = getUserDataDir();
+ }
+ }
+ return localUserDataDir;
+ }
+
+ /**
+ * ベースとなるディレクトリ名を指定し、本アプリケーション名を付与したディレクトリを準備して、
+ * そのパスとなるファイルオブジェクトを返す。
+ * @param baseDir ベースとなるディレクトリ
+ * @return 本アプリケーション用のディレクトリ
+ */
+ private static File ensureAppDataDir(String baseDir) {
+ // システムプロパティ「characterdata.dirname」のディレクトリ名、なければ「CharacterManaJ」を設定する.
+ String characterDirName = System.getProperty("characterdata.dirname", CONFIGURATION_DIR_NAME);
+ File userDataDir = new File(baseDir, characterDirName).getAbsoluteFile();
+
+ // ディレクトリを準備する.
+ if (!userDataDir.exists()) {
+ if (!userDataDir.mkdirs()) {
+ // ログ保管場所も設定されていないのでコンソールに出すしかない.
+ System.err.println("can't create the user data directory. " + userDataDir);
}
}
return userDataDir;
*/
public synchronized static File getApplicationBaseDir() {
if (applicationBaseDir == null) {
-
+
String appbaseDir = System.getProperty("appbase.dir");
if (appbaseDir != null && appbaseDir.length() > 0) {
// 明示的にアプリケーションベースディレクトリが指定されている場合.
if (codeSource == null) {
throw new RuntimeException("codeSource is null: domain=" + pdomain);
}
-
+
URL codeBaseUrl = codeSource.getLocation();
if (!codeBaseUrl.getProtocol().equals("file")) {
throw new RuntimeException("codeLocation is not file protocol.: " + codeBaseUrl);
}
-
+
// クラスパスフォルダ、またはJARファイルの、その親
applicationBaseDir = new File(codeBaseUrl.getPath()).getParentFile();
-
+
}
}
return applicationBaseDir;
}
-
+
/**
* デフォルトのユーザー固有のキャラクターデータディレクトリを取得する.<br>
* ユーザー固有のキャラクターディレクトリがまだ存在しない場合は作成される.<br>
}
return characterBaseDir;
}
-
+
}
/**
* ユーザーデータの保存先を生成するファクトリ
- *
+ *
* @author seraphy
*/
public class UserDataFactory {
*/
private static final Logger logger = Logger.getLogger(UserDataFactory.class.getName());
- /**
- * シングルトン
- */
- private static UserDataFactory inst = new UserDataFactory();
+ private final File userDataDir;
+
+ private UserDataFactory(File userDataDir) {
+ this.userDataDir = userDataDir;
+ }
/**
* インスタンスを取得する.
- *
+ *
* @return インスタンス
*/
public static UserDataFactory getInstance() {
- return inst;
+ return new UserDataFactory(ConfigurationDirUtilities.getUserDataDir());
}
/**
- * プライベートコンストラクタ
+ * ローカルデータ用のインスタンスを取得する.
+ *
+ * @return インスタンス
*/
- private UserDataFactory() {
- super();
+ public static UserDataFactory getLocalInstance() {
+ return new UserDataFactory(ConfigurationDirUtilities.getLocalUserDataDir());
}
/**
* 拡張子を含むファイル名を指定し、そのファイルが保存されるべきユーザディレクトリを判定して返す.<br>
* nullまたは空の場合、もしくは拡張子がない場合はユーザディレクトリのルートを返します.<br>
* フォルダがなければ作成されます.<br>
- *
+ *
* @param name
* ファイル名、もしくはnull
* @return ファィルの拡張子に対応したデータ保存先フォルダ
*/
public File getSpecialDataDir(String name) {
- File userDataDir = ConfigurationDirUtilities.getUserDataDir();
-
+ return getSpecialDataDir(name, userDataDir);
+ }
+
+ /**
+ * 拡張子を含むファイル名を指定し、そのファイルが保存されるべきユーザディレクトリを判定して返す.<br>
+ * nullまたは空の場合、もしくは拡張子がない場合はユーザディレクトリのルートを返します.<br>
+ * フォルダがなければ作成されます.<br>
+ *
+ * @param name ファイル名、もしくはnull
+ * @param userDataDir ユーザーディレクトリ
+ * @return ファィルの拡張子に対応したデータ保存先フォルダ
+ */
+ private File getSpecialDataDir(String name, File userDataDir) {
if (name != null && name.length() > 0) {
int seppos = name.lastIndexOf('-');
if (name.endsWith(".xml") && seppos >= 0) {
boolean result = userDataDir.mkdirs();
logger.log(Level.INFO, "makeDir: " + userDataDir + " /succeeded=" + result);
}
-
+
return userDataDir;
}
/**
* 指定した名前のユーザーデータ保存先を作成する.
- *
+ *
* @param name
* ファイル名
* @return 保存先
/**
* docBaseの名前ベースのUUIDをプレフィックスをもつユーザーデータ保存先を作成する.<br>
- *
+ *
* @param docBase
* URI、null可
* @param name
* docBaseをハッシュ値化文字列にした、名前ベースのUUIDを返す.<br>
* docBaseがnullの場合は空文字とみなして変換する.<br>
* (衝突の可能性は無視する。)<br>
- *
+ *
* @param docBase
* URI、null可
* @return 名前ベースのUUID
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "mangledName " + docBase + "=" + mangledName);
}
-
+
return mangledName;
}
}