OSDN Git Service

mainエントリのパッケージを変更。
authorOlyutorskii <olyutorskii@users.osdn.me>
Sat, 4 Feb 2012 02:01:53 +0000 (11:01 +0900)
committerOlyutorskii <olyutorskii@users.osdn.me>
Sat, 4 Feb 2012 02:01:53 +0000 (11:01 +0900)
機能ごとにサブパッケージに分類。

144 files changed:
CHANGELOG.txt
pom.xml
src/main/java/jp/sfjp/jindolf/Controller.java [moved from src/main/java/jp/sourceforge/jindolf/Controller.java with 89% similarity]
src/main/java/jp/sfjp/jindolf/Jindolf.java [new file with mode: 0644]
src/main/java/jp/sfjp/jindolf/JindolfGuiJp.java [new file with mode: 0644]
src/main/java/jp/sfjp/jindolf/JindolfJre15.java [new file with mode: 0644]
src/main/java/jp/sfjp/jindolf/JindolfOld.java [new file with mode: 0644]
src/main/java/jp/sfjp/jindolf/JreChecker.java [new file with mode: 0644]
src/main/java/jp/sfjp/jindolf/ResourceManager.java [new file with mode: 0644]
src/main/java/jp/sfjp/jindolf/VerInfo.java [new file with mode: 0644]
src/main/java/jp/sfjp/jindolf/config/AppSetting.java [moved from src/main/java/jp/sourceforge/jindolf/AppSetting.java with 71% similarity]
src/main/java/jp/sfjp/jindolf/config/CmdOption.java [moved from src/main/java/jp/sourceforge/jindolf/CmdOption.java with 56% similarity]
src/main/java/jp/sfjp/jindolf/config/ConfigFile.java [moved from src/main/java/jp/sourceforge/jindolf/ConfigFile.java with 61% similarity]
src/main/java/jp/sfjp/jindolf/config/ConfigStore.java [new file with mode: 0644]
src/main/java/jp/sfjp/jindolf/config/EnvInfo.java [moved from src/main/java/jp/sourceforge/jindolf/EnvInfo.java with 88% similarity]
src/main/java/jp/sfjp/jindolf/config/FileUtils.java [moved from src/main/java/jp/sourceforge/jindolf/FileUtils.java with 95% similarity]
src/main/java/jp/sfjp/jindolf/config/InterVMLock.java [moved from src/main/java/jp/sourceforge/jindolf/InterVMLock.java with 99% similarity]
src/main/java/jp/sfjp/jindolf/config/OptionInfo.java [new file with mode: 0644]
src/main/java/jp/sfjp/jindolf/config/package-info.java [new file with mode: 0644]
src/main/java/jp/sfjp/jindolf/data/Anchor.java [moved from src/main/java/jp/sourceforge/jindolf/Anchor.java with 99% similarity]
src/main/java/jp/sfjp/jindolf/data/Avatar.java [moved from src/main/java/jp/sourceforge/jindolf/Avatar.java with 99% similarity]
src/main/java/jp/sfjp/jindolf/data/DialogPref.java [moved from src/main/java/jp/sourceforge/jindolf/DialogPref.java with 99% similarity]
src/main/java/jp/sfjp/jindolf/data/Land.java [moved from src/main/java/jp/sourceforge/jindolf/Land.java with 96% similarity]
src/main/java/jp/sfjp/jindolf/data/LandsModel.java [moved from src/main/java/jp/sourceforge/jindolf/LandsModel.java with 97% similarity]
src/main/java/jp/sfjp/jindolf/data/Period.java [moved from src/main/java/jp/sourceforge/jindolf/Period.java with 99% similarity]
src/main/java/jp/sfjp/jindolf/data/Player.java [moved from src/main/java/jp/sourceforge/jindolf/Player.java with 99% similarity]
src/main/java/jp/sfjp/jindolf/data/RegexPattern.java [moved from src/main/java/jp/sourceforge/jindolf/RegexPattern.java with 99% similarity]
src/main/java/jp/sfjp/jindolf/data/SysEvent.java [moved from src/main/java/jp/sourceforge/jindolf/SysEvent.java with 99% similarity]
src/main/java/jp/sfjp/jindolf/data/Talk.java [moved from src/main/java/jp/sourceforge/jindolf/Talk.java with 99% similarity]
src/main/java/jp/sfjp/jindolf/data/Topic.java [moved from src/main/java/jp/sourceforge/jindolf/Topic.java with 91% similarity]
src/main/java/jp/sfjp/jindolf/data/Village.java [moved from src/main/java/jp/sourceforge/jindolf/Village.java with 98% similarity]
src/main/java/jp/sfjp/jindolf/dxchg/ClipboardAction.java [moved from src/main/java/jp/sourceforge/jindolf/ClipboardAction.java with 99% similarity]
src/main/java/jp/sfjp/jindolf/dxchg/CsvExporter.java [moved from src/main/java/jp/sourceforge/jindolf/CsvExporter.java with 98% similarity]
src/main/java/jp/sfjp/jindolf/dxchg/FaceIconSet.java [moved from src/main/java/jp/sourceforge/jindolf/FaceIconSet.java with 96% similarity]
src/main/java/jp/sfjp/jindolf/dxchg/TextPopup.java [moved from src/main/java/jp/sourceforge/jindolf/TextPopup.java with 99% similarity]
src/main/java/jp/sfjp/jindolf/dxchg/UriExporter.java [moved from src/main/java/jp/sourceforge/jindolf/UriExporter.java with 98% similarity]
src/main/java/jp/sfjp/jindolf/dxchg/WebButton.java [moved from src/main/java/jp/sourceforge/jindolf/WebButton.java with 97% similarity]
src/main/java/jp/sfjp/jindolf/dxchg/WebIPC.java [moved from src/main/java/jp/sourceforge/jindolf/WebIPC.java with 99% similarity]
src/main/java/jp/sfjp/jindolf/dxchg/WebIPCDialog.java [moved from src/main/java/jp/sourceforge/jindolf/WebIPCDialog.java with 95% similarity]
src/main/java/jp/sfjp/jindolf/dxchg/WolfBBS.java [moved from src/main/java/jp/sourceforge/jindolf/WolfBBS.java with 93% similarity]
src/main/java/jp/sfjp/jindolf/dxchg/XmlResourceResolver.java [moved from src/main/java/jp/sourceforge/jindolf/XmlResourceResolver.java with 99% similarity]
src/main/java/jp/sfjp/jindolf/dxchg/XmlUtils.java [moved from src/main/java/jp/sourceforge/jindolf/XmlUtils.java with 99% similarity]
src/main/java/jp/sfjp/jindolf/dxchg/package-info.java [new file with mode: 0644]
src/main/java/jp/sfjp/jindolf/editor/BalloonBorder.java [moved from src/main/java/jp/sourceforge/jindolf/BalloonBorder.java with 99% similarity]
src/main/java/jp/sfjp/jindolf/editor/EditArray.java [moved from src/main/java/jp/sourceforge/jindolf/EditArray.java with 99% similarity]
src/main/java/jp/sfjp/jindolf/editor/TalkEditor.java [moved from src/main/java/jp/sourceforge/jindolf/TalkEditor.java with 99% similarity]
src/main/java/jp/sfjp/jindolf/editor/TalkPreview.java [moved from src/main/java/jp/sourceforge/jindolf/TalkPreview.java with 91% similarity]
src/main/java/jp/sfjp/jindolf/editor/TextEditor.java [moved from src/main/java/jp/sourceforge/jindolf/TextEditor.java with 99% similarity]
src/main/java/jp/sfjp/jindolf/editor/package-info.java [new file with mode: 0644]
src/main/java/jp/sfjp/jindolf/glyph/AbstractTextRow.java [moved from src/main/java/jp/sourceforge/jindolf/AbstractTextRow.java with 99% similarity]
src/main/java/jp/sfjp/jindolf/glyph/AnchorDraw.java [moved from src/main/java/jp/sourceforge/jindolf/AnchorDraw.java with 97% similarity]
src/main/java/jp/sfjp/jindolf/glyph/AnchorHitEvent.java [moved from src/main/java/jp/sourceforge/jindolf/AnchorHitEvent.java with 95% similarity]
src/main/java/jp/sfjp/jindolf/glyph/AnchorHitListener.java [moved from src/main/java/jp/sourceforge/jindolf/AnchorHitListener.java with 93% similarity]
src/main/java/jp/sfjp/jindolf/glyph/Discussion.java [moved from src/main/java/jp/sourceforge/jindolf/Discussion.java with 98% similarity]
src/main/java/jp/sfjp/jindolf/glyph/FontChooser.java [moved from src/main/java/jp/sourceforge/jindolf/FontChooser.java with 52% similarity]
src/main/java/jp/sfjp/jindolf/glyph/FontInfo.java [moved from src/main/java/jp/sourceforge/jindolf/FontInfo.java with 56% similarity]
src/main/java/jp/sfjp/jindolf/glyph/FontListModel.java [new file with mode: 0644]
src/main/java/jp/sfjp/jindolf/glyph/FontPreviewer.java [new file with mode: 0644]
src/main/java/jp/sfjp/jindolf/glyph/FontSelectList.java [new file with mode: 0644]
src/main/java/jp/sfjp/jindolf/glyph/FontUtils.java [moved from src/main/java/jp/sourceforge/jindolf/FontUtils.java with 61% similarity]
src/main/java/jp/sfjp/jindolf/glyph/GlyphDraw.java [moved from src/main/java/jp/sourceforge/jindolf/GlyphDraw.java with 99% similarity]
src/main/java/jp/sfjp/jindolf/glyph/ImtblAffineTx.java [moved from src/main/java/jp/sourceforge/jindolf/ImtblAffineTx.java with 99% similarity]
src/main/java/jp/sfjp/jindolf/glyph/Selectable.java [moved from src/main/java/jp/sourceforge/jindolf/Selectable.java with 96% similarity]
src/main/java/jp/sfjp/jindolf/glyph/SequenceCharacterIterator.java [moved from src/main/java/jp/sourceforge/jindolf/SequenceCharacterIterator.java with 99% similarity]
src/main/java/jp/sfjp/jindolf/glyph/SysEventDraw.java [moved from src/main/java/jp/sourceforge/jindolf/SysEventDraw.java with 98% similarity]
src/main/java/jp/sfjp/jindolf/glyph/TalkDraw.java [moved from src/main/java/jp/sourceforge/jindolf/TalkDraw.java with 98% similarity]
src/main/java/jp/sfjp/jindolf/glyph/TextRow.java [moved from src/main/java/jp/sourceforge/jindolf/TextRow.java with 98% similarity]
src/main/java/jp/sfjp/jindolf/glyph/package-info.java [new file with mode: 0644]
src/main/java/jp/sfjp/jindolf/log/LogFrame.java [moved from src/main/java/jp/sourceforge/jindolf/LogFrame.java with 73% similarity]
src/main/java/jp/sfjp/jindolf/log/LogUtils.java [new file with mode: 0644]
src/main/java/jp/sfjp/jindolf/log/LogWrapper.java [moved from src/main/java/jp/sourceforge/jindolf/LogWrapper.java with 94% similarity]
src/main/java/jp/sfjp/jindolf/log/LoggingDispatcher.java [new file with mode: 0644]
src/main/java/jp/sfjp/jindolf/log/PileHandler.java [moved from src/main/java/jp/sourceforge/jindolf/PileHandler.java with 51% similarity]
src/main/java/jp/sfjp/jindolf/log/SwingDocHandler.java [new file with mode: 0644]
src/main/java/jp/sfjp/jindolf/log/package-info.java [new file with mode: 0644]
src/main/java/jp/sfjp/jindolf/net/AccountCookie.java [moved from src/main/java/jp/sourceforge/jindolf/AccountCookie.java with 99% similarity]
src/main/java/jp/sfjp/jindolf/net/HtmlSequence.java [moved from src/main/java/jp/sourceforge/jindolf/HtmlSequence.java with 98% similarity]
src/main/java/jp/sfjp/jindolf/net/HttpUtils.java [moved from src/main/java/jp/sourceforge/jindolf/HttpUtils.java with 97% similarity]
src/main/java/jp/sfjp/jindolf/net/ProxyChooser.java [moved from src/main/java/jp/sourceforge/jindolf/ProxyChooser.java with 98% similarity]
src/main/java/jp/sfjp/jindolf/net/ProxyInfo.java [moved from src/main/java/jp/sourceforge/jindolf/ProxyInfo.java with 99% similarity]
src/main/java/jp/sfjp/jindolf/net/ServerAccess.java [moved from src/main/java/jp/sourceforge/jindolf/ServerAccess.java with 98% similarity]
src/main/java/jp/sfjp/jindolf/net/TallyInputStream.java [moved from src/main/java/jp/sourceforge/jindolf/TallyInputStream.java with 96% similarity]
src/main/java/jp/sfjp/jindolf/net/TallyOutputStream.java [moved from src/main/java/jp/sourceforge/jindolf/TallyOutputStream.java with 95% similarity]
src/main/java/jp/sfjp/jindolf/net/package-info.java [new file with mode: 0644]
src/main/java/jp/sfjp/jindolf/package-info.java [moved from src/main/java/jp/sourceforge/jindolf/package-info.java with 82% similarity]
src/main/java/jp/sfjp/jindolf/summary/DaySummary.java [moved from src/main/java/jp/sourceforge/jindolf/DaySummary.java with 97% similarity]
src/main/java/jp/sfjp/jindolf/summary/GameSummary.java [moved from src/main/java/jp/sourceforge/jindolf/GameSummary.java with 95% similarity]
src/main/java/jp/sfjp/jindolf/summary/VillageDigest.java [moved from src/main/java/jp/sourceforge/jindolf/VillageDigest.java with 98% similarity]
src/main/java/jp/sfjp/jindolf/summary/package-info.java [new file with mode: 0644]
src/main/java/jp/sfjp/jindolf/util/GUIUtils.java [moved from src/main/java/jp/sourceforge/jindolf/GUIUtils.java with 78% similarity]
src/main/java/jp/sfjp/jindolf/util/Monodizer.java [moved from src/main/java/jp/sourceforge/jindolf/Monodizer.java with 99% similarity]
src/main/java/jp/sfjp/jindolf/util/StringUtils.java [moved from src/main/java/jp/sourceforge/jindolf/StringUtils.java with 99% similarity]
src/main/java/jp/sfjp/jindolf/util/package-info.java [new file with mode: 0644]
src/main/java/jp/sfjp/jindolf/view/AccountPanel.java [moved from src/main/java/jp/sourceforge/jindolf/AccountPanel.java with 95% similarity]
src/main/java/jp/sfjp/jindolf/view/ActionManager.java [moved from src/main/java/jp/sourceforge/jindolf/ActionManager.java with 95% similarity]
src/main/java/jp/sfjp/jindolf/view/DialogPrefPanel.java [moved from src/main/java/jp/sourceforge/jindolf/DialogPrefPanel.java with 98% similarity]
src/main/java/jp/sfjp/jindolf/view/FilterPanel.java [moved from src/main/java/jp/sourceforge/jindolf/FilterPanel.java with 98% similarity]
src/main/java/jp/sfjp/jindolf/view/FindPanel.java [moved from src/main/java/jp/sourceforge/jindolf/FindPanel.java with 94% similarity]
src/main/java/jp/sfjp/jindolf/view/HelpFrame.java [moved from src/main/java/jp/sourceforge/jindolf/HelpFrame.java with 81% similarity]
src/main/java/jp/sfjp/jindolf/view/LandInfoPanel.java [moved from src/main/java/jp/sourceforge/jindolf/LandInfoPanel.java with 97% similarity]
src/main/java/jp/sfjp/jindolf/view/LandsTree.java [moved from src/main/java/jp/sourceforge/jindolf/LandsTree.java with 92% similarity]
src/main/java/jp/sfjp/jindolf/view/LockErrorPane.java [new file with mode: 0644]
src/main/java/jp/sfjp/jindolf/view/OptionPanel.java [moved from src/main/java/jp/sourceforge/jindolf/OptionPanel.java with 95% similarity]
src/main/java/jp/sfjp/jindolf/view/PeriodView.java [moved from src/main/java/jp/sourceforge/jindolf/PeriodView.java with 96% similarity]
src/main/java/jp/sfjp/jindolf/view/TabBrowser.java [moved from src/main/java/jp/sourceforge/jindolf/TabBrowser.java with 97% similarity]
src/main/java/jp/sfjp/jindolf/view/TopView.java [moved from src/main/java/jp/sourceforge/jindolf/TopView.java with 95% similarity]
src/main/java/jp/sfjp/jindolf/view/TopicFilter.java [moved from src/main/java/jp/sourceforge/jindolf/TopicFilter.java with 93% similarity]
src/main/java/jp/sfjp/jindolf/view/VillageIconRenderer.java [moved from src/main/java/jp/sourceforge/jindolf/VillageIconRenderer.java with 78% similarity]
src/main/java/jp/sfjp/jindolf/view/VillageInfoPanel.java [moved from src/main/java/jp/sourceforge/jindolf/VillageInfoPanel.java with 98% similarity]
src/main/java/jp/sfjp/jindolf/view/package-info.java [new file with mode: 0644]
src/main/java/jp/sourceforge/jindolf/Jindolf.java [deleted file]
src/main/java/jp/sourceforge/jindolf/OptionInfo.java [deleted file]
src/main/resources/jp/sfjp/jindolf/resources/font/preview.txt [moved from src/main/resources/jp/sourceforge/jindolf/resources/preview.txt with 100% similarity]
src/main/resources/jp/sfjp/jindolf/resources/help.txt [moved from src/main/resources/jp/sourceforge/jindolf/resources/help.txt with 100% similarity]
src/main/resources/jp/sfjp/jindolf/resources/html/help.css [moved from src/main/resources/jp/sourceforge/jindolf/resources/html/help.css with 100% similarity]
src/main/resources/jp/sfjp/jindolf/resources/html/help.html [moved from src/main/resources/jp/sourceforge/jindolf/resources/html/help.html with 100% similarity]
src/main/resources/jp/sfjp/jindolf/resources/html/keyassign.html [moved from src/main/resources/jp/sourceforge/jindolf/resources/html/keyassign.html with 100% similarity]
src/main/resources/jp/sfjp/jindolf/resources/html/license.html [moved from src/main/resources/jp/sourceforge/jindolf/resources/html/license.html with 100% similarity]
src/main/resources/jp/sfjp/jindolf/resources/html/options.html [moved from src/main/resources/jp/sourceforge/jindolf/resources/html/options.html with 100% similarity]
src/main/resources/jp/sfjp/jindolf/resources/image/logo.png [moved from src/main/resources/jp/sourceforge/jindolf/resources/image/logo.png with 100% similarity]
src/main/resources/jp/sfjp/jindolf/resources/image/noimage.png [moved from src/main/resources/jp/sourceforge/jindolf/resources/image/noimage.png with 100% similarity]
src/main/resources/jp/sfjp/jindolf/resources/image/tb_ascend.png [moved from src/main/resources/jp/sourceforge/jindolf/resources/image/ascend.png with 100% similarity]
src/main/resources/jp/sfjp/jindolf/resources/image/tb_descend.png [moved from src/main/resources/jp/sourceforge/jindolf/resources/image/descend.png with 100% similarity]
src/main/resources/jp/sfjp/jindolf/resources/image/tb_editor.png [moved from src/main/resources/jp/sourceforge/jindolf/resources/image/editor.png with 100% similarity]
src/main/resources/jp/sfjp/jindolf/resources/image/tb_filter.png [moved from src/main/resources/jp/sourceforge/jindolf/resources/image/filter.png with 100% similarity]
src/main/resources/jp/sfjp/jindolf/resources/image/tb_find.png [moved from src/main/resources/jp/sourceforge/jindolf/resources/image/find.png with 100% similarity]
src/main/resources/jp/sfjp/jindolf/resources/image/tb_findnext.png [moved from src/main/resources/jp/sourceforge/jindolf/resources/image/findnext.png with 100% similarity]
src/main/resources/jp/sfjp/jindolf/resources/image/tb_findprev.png [moved from src/main/resources/jp/sourceforge/jindolf/resources/image/findprev.png with 100% similarity]
src/main/resources/jp/sfjp/jindolf/resources/image/tb_reload.png [moved from src/main/resources/jp/sourceforge/jindolf/resources/image/reload.png with 100% similarity]
src/main/resources/jp/sfjp/jindolf/resources/image/vs_cross.png [moved from src/main/resources/jp/sourceforge/jindolf/resources/image/cross.png with 100% similarity]
src/main/resources/jp/sfjp/jindolf/resources/image/vs_epilogue.png [moved from src/main/resources/jp/sourceforge/jindolf/resources/image/epilogue.png with 100% similarity]
src/main/resources/jp/sfjp/jindolf/resources/image/vs_gameover.png [moved from src/main/resources/jp/sourceforge/jindolf/resources/image/gameover.png with 100% similarity]
src/main/resources/jp/sfjp/jindolf/resources/image/vs_progress.png [moved from src/main/resources/jp/sourceforge/jindolf/resources/image/progress.png with 100% similarity]
src/main/resources/jp/sfjp/jindolf/resources/image/vs_prologue.png [moved from src/main/resources/jp/sourceforge/jindolf/resources/image/prologue.png with 100% similarity]
src/main/resources/jp/sfjp/jindolf/resources/image/winicon.png [moved from src/main/resources/jp/sourceforge/jindolf/resources/image/winicon.png with 100% similarity]
src/main/resources/jp/sfjp/jindolf/resources/image/www.png [moved from src/main/resources/jp/sourceforge/jindolf/resources/image/www.png with 100% similarity]
src/main/resources/jp/sfjp/jindolf/resources/version.properties [new file with mode: 0644]
src/main/resources/jp/sfjp/jindolf/resources/wolfbbs/faceIconSet.properties [moved from src/main/resources/jp/sourceforge/jindolf/resources/faceIconSet.properties with 100% similarity]
src/main/resources/jp/sourceforge/jindolf/resources/version.properties [deleted file]
src/test/java/jp/sfjp/jindolf/config/CmdOptionTest.java [new file with mode: 0644]
src/test/java/jp/sfjp/jindolf/config/OptionInfoTest.java [new file with mode: 0644]
src/test/java/jp/sfjp/jindolf/data/AvatarTest.java [moved from src/test/java/jp/sourceforge/jindolf/AvatarTest.java with 99% similarity]
src/test/java/jp/sfjp/jindolf/net/HttpUtilsTest.java [moved from src/test/java/jp/sourceforge/jindolf/HttpUtilsTest.java with 98% similarity]
src/test/java/jp/sfjp/jindolf/util/StringUtilsTest.java [moved from src/test/java/jp/sourceforge/jindolf/StringUtilsTest.java with 99% similarity]

index 5bb6363..0a2d842 100644 (file)
@@ -10,6 +10,7 @@ Jindolf 変更履歴
     ・ninjin氏の連絡先を http://ninjinix.com へ変更。
     ・UIの各種文言を修正。
     ・L&Fを「MacOSX」から変更する際の例外に対処。(バグ報告#26564)
+    ・パッケージ構成の整理。
 
 3.206.4 (2011-05-10)
     ・10億をこえる発言番号へのアンカー参照を抑止。(バグ報告#24477,#24946)
diff --git a/pom.xml b/pom.xml
index f05d6f1..69dac4b 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -95,8 +95,8 @@
         <checkstyle.config.location>${project.mainconf}/checks.xml</checkstyle.config.location>
         <checkstyle.enable.rss>false</checkstyle.enable.rss>
 
-        <project.mainentry>jp.sourceforge.jindolf.Jindolf</project.mainentry>
-        <project.splash>jp/sourceforge/jindolf/resources/image/logo.png</project.splash>
+        <project.mainentry>jp.sfjp.jindolf.Jindolf</project.mainentry>
+        <project.splash>jp/sfjp/jindolf/resources/image/logo.png</project.splash>
 
     </properties>
 
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-jar-plugin</artifactId>
-                <version>2.3.2</version>
+                <version>2.4</version>
                 <configuration>
                     <archive>
                         <manifest>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-assembly-plugin</artifactId>
-                <version>2.2.1</version>
+                <version>2.3</version>
                 <configuration>
                     <descriptors>
                         <descriptor>src/main/assembly/descriptor.xml</descriptor>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-javadoc-plugin</artifactId>
-                <version>2.8</version>
+                <version>2.8.1</version>
                 <configuration>
                     <skip>false</skip>
                     <notimestamp>true</notimestamp>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-surefire-report-plugin</artifactId>
-                <version>2.10</version>
+                <version>2.12</version>
                 <configuration>
                     <showSuccess>false</showSuccess>
                 </configuration>
             <plugin>
                 <groupId>org.codehaus.mojo</groupId>
                 <artifactId>findbugs-maven-plugin</artifactId>
-                <version>2.3.2</version>
+                <version>2.4.0</version>
                 <configuration>
                     <skip>true</skip>
                     <effort>Max</effort>
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf;
 
 import java.awt.BorderLayout;
 import java.awt.Component;
@@ -55,8 +55,47 @@ import javax.swing.event.TreeSelectionEvent;
 import javax.swing.event.TreeSelectionListener;
 import javax.swing.event.TreeWillExpandListener;
 import javax.swing.tree.TreePath;
+import jp.sfjp.jindolf.config.AppSetting;
+import jp.sfjp.jindolf.config.ConfigStore;
+import jp.sfjp.jindolf.config.OptionInfo;
+import jp.sfjp.jindolf.data.Anchor;
+import jp.sfjp.jindolf.data.DialogPref;
+import jp.sfjp.jindolf.data.Land;
+import jp.sfjp.jindolf.data.LandsModel;
+import jp.sfjp.jindolf.data.Period;
+import jp.sfjp.jindolf.data.RegexPattern;
+import jp.sfjp.jindolf.data.Talk;
+import jp.sfjp.jindolf.data.Village;
+import jp.sfjp.jindolf.dxchg.CsvExporter;
+import jp.sfjp.jindolf.dxchg.WebIPCDialog;
+import jp.sfjp.jindolf.editor.TalkPreview;
+import jp.sfjp.jindolf.glyph.AnchorHitEvent;
+import jp.sfjp.jindolf.glyph.AnchorHitListener;
+import jp.sfjp.jindolf.glyph.Discussion;
+import jp.sfjp.jindolf.glyph.FontInfo;
+import jp.sfjp.jindolf.glyph.TalkDraw;
+import jp.sfjp.jindolf.log.LogFrame;
+import jp.sfjp.jindolf.log.LogUtils;
+import jp.sfjp.jindolf.log.LogWrapper;
+import jp.sfjp.jindolf.net.ProxyInfo;
+import jp.sfjp.jindolf.net.ServerAccess;
+import jp.sfjp.jindolf.summary.DaySummary;
+import jp.sfjp.jindolf.summary.VillageDigest;
+import jp.sfjp.jindolf.util.GUIUtils;
+import jp.sfjp.jindolf.util.StringUtils;
+import jp.sfjp.jindolf.view.AccountPanel;
+import jp.sfjp.jindolf.view.ActionManager;
+import jp.sfjp.jindolf.view.FilterPanel;
+import jp.sfjp.jindolf.view.FindPanel;
+import jp.sfjp.jindolf.view.HelpFrame;
+import jp.sfjp.jindolf.view.LandsTree;
+import jp.sfjp.jindolf.view.OptionPanel;
+import jp.sfjp.jindolf.view.PeriodView;
+import jp.sfjp.jindolf.view.TabBrowser;
+import jp.sfjp.jindolf.view.TopView;
 import jp.sourceforge.jindolf.corelib.LandDef;
 import jp.sourceforge.jindolf.corelib.VillageState;
+import jp.sourceforge.jovsonz.JsObject;
 
 /**
  * いわゆるMVCでいうとこのコントローラ。
@@ -68,6 +107,29 @@ public class Controller
                    ChangeListener,
                    AnchorHitListener {
 
+    private static final String TITLE_LOGGER =
+            VerInfo.getFrameTitle("ログ表示");
+    private static final String TITLE_FILTER =
+            VerInfo.getFrameTitle("発言フィルタ");
+    private static final String TITLE_EDITOR =
+            VerInfo.getFrameTitle("発言エディタ");
+    private static final String TITLE_OPTION =
+            VerInfo.getFrameTitle("オプション設定");
+    private static final String TITLE_FIND =
+            VerInfo.getFrameTitle("発言検索");
+    private static final String TITLE_ACCOUNT =
+            VerInfo.getFrameTitle("アカウント管理");
+    private static final String TITLE_DIGEST =
+            VerInfo.getFrameTitle("村のダイジェスト");
+    private static final String TITLE_DAYSUMMARY =
+            VerInfo.getFrameTitle("発言集計");
+    private static final String TITLE_HELP =
+            VerInfo.getFrameTitle("ヘルプ");
+
+    private static final LogWrapper LOGGER = new LogWrapper();
+
+
+    private final AppSetting appSetting;
     private final ActionManager actionManager;
     private final TopView topView;
     private final LandsModel model;
@@ -90,15 +152,19 @@ public class Controller
 
     /**
      * コントローラの生成。
+     * @param setting アプリ設定
      * @param actionManager アクション管理
      * @param topView 最上位ビュー
      * @param model 最上位データモデル
      */
-    public Controller(ActionManager actionManager,
+    @SuppressWarnings("LeakingThisInConstructor")
+    public Controller(AppSetting setting,
+                       ActionManager actionManager,
                        TopView topView,
                        LandsModel model){
         super();
 
+        this.appSetting = setting;
         this.actionManager = actionManager;
         this.topView = topView;
         this.model = model;
@@ -124,43 +190,37 @@ public class Controller
         reloadVillageListButton.setEnabled(false);
 
         this.filterFrame = new FilterPanel(this.topFrame);
+        this.filterFrame.setTitle(TITLE_FILTER);
         this.filterFrame.addChangeListener(this);
         this.filterFrame.pack();
         this.filterFrame.setVisible(false);
 
         this.showlogFrame = new LogFrame(this.topFrame);
+        this.showlogFrame.setTitle(TITLE_LOGGER);
         this.showlogFrame.pack();
         this.showlogFrame.setSize(600, 500);
         this.showlogFrame.setLocationByPlatform(true);
         this.showlogFrame.setVisible(false);
-        if(Jindolf.hasLoggingPermission()){
-            Handler newHandler = this.showlogFrame.getHandler();
-            Logger jre14Logger = Jindolf.logger().getJre14Logger();
-            jre14Logger.addHandler(newHandler);
-            Handler[] handlers = jre14Logger.getHandlers();
-            for(Handler handler : handlers){
-                if( ! (handler instanceof PileHandler) ) continue;
-                PileHandler pile = (PileHandler) handler;
-                pile.delegate(newHandler);
-                pile.close();
-            }
-        }
+        Logger rootLogger = Logger.getLogger("");
+        Handler newHandler = this.showlogFrame.getHandler();
+        LogUtils.switchHandler(rootLogger, newHandler);
 
         this.talkPreview = new TalkPreview();
+        this.talkPreview.setTitle(TITLE_EDITOR);
         this.talkPreview.pack();
         this.talkPreview.setSize(700, 500);
         this.talkPreview.setVisible(false);
-        this.talkPreview.loadDraft();
 
         this.optionPanel = new OptionPanel(this.topFrame);
+        this.optionPanel.setTitle(TITLE_OPTION);
         this.optionPanel.pack();
         this.optionPanel.setSize(450, 500);
         this.optionPanel.setVisible(false);
 
         this.findPanel = new FindPanel(this.topFrame);
+        this.findPanel.setTitle(TITLE_FIND);
         this.findPanel.pack();
         this.findPanel.setVisible(false);
-        this.findPanel.loadHistory();
 
         this.windowMap.put(this.filterFrame,  true);
         this.windowMap.put(this.showlogFrame, false);
@@ -168,17 +228,23 @@ public class Controller
         this.windowMap.put(this.optionPanel,  false);
         this.windowMap.put(this.findPanel,    true);
 
-        AppSetting setting = Jindolf.getAppSetting();
+        ConfigStore config = this.appSetting.getConfigStore();
+
+        JsObject draft = config.loadDraftConfig();
+        this.talkPreview.putJson(draft);
 
-        FontInfo fontInfo = setting.getFontInfo();
+        JsObject history = config.loadHistoryConfig();
+        this.findPanel.putJson(history);
+
+        FontInfo fontInfo = this.appSetting.getFontInfo();
         this.topView.getTabBrowser().setFontInfo(fontInfo);
         this.talkPreview.setFontInfo(fontInfo);
         this.optionPanel.getFontChooser().setFontInfo(fontInfo);
 
-        ProxyInfo proxyInfo = setting.getProxyInfo();
+        ProxyInfo proxyInfo = this.appSetting.getProxyInfo();
         this.optionPanel.getProxyChooser().setProxyInfo(proxyInfo);
 
-        DialogPref pref = setting.getDialogPref();
+        DialogPref pref = this.appSetting.getDialogPref();
         this.topView.getTabBrowser().setDialogPref(pref);
         this.optionPanel.getDialogPrefPanel().setDialogPref(pref);
 
@@ -224,24 +290,14 @@ public class Controller
      * About画面を表示する。
      */
     private void actionAbout(){
-        String message =
-                Jindolf.TITLE
-                + "   Version " + Jindolf.VERSION + "\n"
-                + Jindolf.COPYRIGHT + "\n"
-                + "ライセンス: " + Jindolf.LICENSE + "\n"
-                + "連絡先: " + Jindolf.CONTACT;
-
-        if(Jindolf.COMMENT.length() > 0){
-            message += "\n" + Jindolf.COMMENT;
-        }
-
+        String message = VerInfo.getAboutMessage();
         JOptionPane pane = new JOptionPane(message,
                                            JOptionPane.INFORMATION_MESSAGE,
                                            JOptionPane.DEFAULT_OPTION,
                                            GUIUtils.getLogoIcon());
 
         JDialog dialog = pane.createDialog(this.topFrame,
-                                           Jindolf.TITLE + "について");
+                                           VerInfo.TITLE + "について");
 
         dialog.pack();
         dialog.setVisible(true);
@@ -267,7 +323,11 @@ public class Controller
             return;
         }
 
-        this.helpFrame = new HelpFrame();
+        OptionInfo optInfo = this.appSetting.getOptionInfo();
+        ConfigStore configStore = this.appSetting.getConfigStore();
+
+        this.helpFrame = new HelpFrame(optInfo, configStore);
+        this.helpFrame.setTitle(TITLE_HELP);
         this.helpFrame.pack();
         this.helpFrame.setSize(450, 450);
 
@@ -420,7 +480,7 @@ public class Controller
      * ポータルサイトをWebブラウザで表示する。
      */
     private void actionShowPortal(){
-        WebIPCDialog.showDialog(this.topFrame, Jindolf.CONTACT);
+        WebIPCDialog.showDialog(this.topFrame, VerInfo.CONTACT);
         return;
     }
 
@@ -431,11 +491,11 @@ public class Controller
      * @param e 例外
      */
     private void warnDialog(String title, String message, Throwable e){
-        Jindolf.logger().warn(message, e);
+        LOGGER.warn(message, e);
         JOptionPane.showMessageDialog(
             this.topFrame,
             message,
-            title + " - " + Jindolf.TITLE,
+            VerInfo.getFrameTitle(title),
             JOptionPane.WARNING_MESSAGE );
         return;
     }
@@ -481,7 +541,7 @@ public class Controller
             return;
         }
 
-        Jindolf.logger().info(
+        LOGGER.info(
                 "Look&Feelが["
                 +lnf.getName()
                 +"]に変更されました。");
@@ -510,10 +570,10 @@ public class Controller
                 try{
                     SwingUtilities.invokeAndWait(updateUITask);
                 }catch(InvocationTargetException e){
-                    Jindolf.logger().warn(
+                    LOGGER.warn(
                             "Look&Feelの更新に失敗しました。", e);
                 }catch(InterruptedException e){
-                    Jindolf.logger().warn(
+                    LOGGER.warn(
                             "Look&Feelの更新に失敗しました。", e);
                 }finally{
                     updateStatusBar("Look&Feelが更新されました");
@@ -544,6 +604,7 @@ public class Controller
         }
 
         this.accountFrame = new AccountPanel(this.topFrame, this.model);
+        this.accountFrame.setTitle(TITLE_ACCOUNT);
         this.accountFrame.pack();
         this.accountFrame.setVisible(true);
 
@@ -572,15 +633,13 @@ public class Controller
      * オプション設定画面を表示する。
      */
     private void actionOption(){
-        AppSetting setting = Jindolf.getAppSetting();
-
-        FontInfo fontInfo = setting.getFontInfo();
+        FontInfo fontInfo = this.appSetting.getFontInfo();
         this.optionPanel.getFontChooser().setFontInfo(fontInfo);
 
-        ProxyInfo proxyInfo = setting.getProxyInfo();
+        ProxyInfo proxyInfo = this.appSetting.getProxyInfo();
         this.optionPanel.getProxyChooser().setProxyInfo(proxyInfo);
 
-        DialogPref dialogPref = setting.getDialogPref();
+        DialogPref dialogPref = this.appSetting.getDialogPref();
         this.optionPanel.getDialogPrefPanel().setDialogPref(dialogPref);
 
         this.optionPanel.setVisible(true);
@@ -603,11 +662,10 @@ public class Controller
      * @param newFontInfo 新フォント設定
      */
     private void updateFontInfo(final FontInfo newFontInfo){
-        AppSetting setting = Jindolf.getAppSetting();
-        FontInfo oldInfo = setting.getFontInfo();
+        FontInfo oldInfo = this.appSetting.getFontInfo();
 
         if(newFontInfo.equals(oldInfo)) return;
-        setting.setFontInfo(newFontInfo);
+        this.appSetting.setFontInfo(newFontInfo);
 
         this.topView.getTabBrowser().setFontInfo(newFontInfo);
         this.talkPreview.setFontInfo(newFontInfo);
@@ -621,11 +679,10 @@ public class Controller
      * @param newProxyInfo 新プロクシ設定
      */
     private void updateProxyInfo(ProxyInfo newProxyInfo){
-        AppSetting setting = Jindolf.getAppSetting();
-        ProxyInfo oldProxyInfo = setting.getProxyInfo();
+        ProxyInfo oldProxyInfo = this.appSetting.getProxyInfo();
 
         if(newProxyInfo.equals(oldProxyInfo)) return;
-        setting.setProxyInfo(newProxyInfo);
+        this.appSetting.setProxyInfo(newProxyInfo);
 
         for(Land land : this.model.getLandList()){
             ServerAccess server = land.getServerAccess();
@@ -640,11 +697,10 @@ public class Controller
      * @param newDialogPref 表示設定
      */
     private void updateDialogPref(DialogPref newDialogPref){
-        AppSetting setting = Jindolf.getAppSetting();
-        DialogPref oldDialogPref = setting.getDialogPref();
+        DialogPref oldDialogPref = this.appSetting.getDialogPref();
 
         if(newDialogPref.equals(oldDialogPref)) return;
-        setting.setDialogPref(newDialogPref);
+        this.appSetting.setDialogPref(newDialogPref);
 
         this.topView.getTabBrowser().setDialogPref(newDialogPref);
 
@@ -665,7 +721,7 @@ public class Controller
            ) || ! village.isValid() ){
             String message = "エピローグを正常に迎えていない村は\n"
                             +"ダイジェスト機能を利用できません";
-            String title = "ダイジェスト不可 - " + Jindolf.TITLE;
+            String title = VerInfo.getFrameTitle("ダイジェスト不可");
             JOptionPane pane = new JOptionPane(message,
                                                JOptionPane.WARNING_MESSAGE,
                                                JOptionPane.DEFAULT_OPTION );
@@ -678,6 +734,7 @@ public class Controller
 
         if(this.digestPanel == null){
             this.digestPanel = new VillageDigest(this.topFrame);
+            this.digestPanel.setTitle(TITLE_DIGEST);
             this.digestPanel.pack();
             this.digestPanel.setSize(600, 550);
             this.windowMap.put(this.digestPanel, false);
@@ -774,7 +831,7 @@ public class Controller
             }
         }
         loginfo += hitMessage;
-        Jindolf.logger().info(loginfo);
+        LOGGER.info(loginfo);
 
         return;
     }
@@ -825,7 +882,7 @@ public class Controller
             }
         }
         loginfo += hitMessage;
-        Jindolf.logger().info(loginfo);
+        LOGGER.info(loginfo);
 
         return;
     }
@@ -853,6 +910,7 @@ public class Controller
 
         if(this.daySummaryPanel == null){
             this.daySummaryPanel = new DaySummary(this.topFrame);
+            this.daySummaryPanel.setTitle(TITLE_DAYSUMMARY);
             this.daySummaryPanel.pack();
             this.daySummaryPanel.setSize(400, 500);
         }
@@ -1205,10 +1263,10 @@ public class Controller
                         }
                     });
                 }catch(InvocationTargetException e){
-                    Jindolf.logger().fatal(
+                    LOGGER.fatal(
                             "タブ操作で致命的な障害が発生しました", e);
                 }catch(InterruptedException e){
-                    Jindolf.logger().fatal(
+                    LOGGER.fatal(
                             "タブ操作で致命的な障害が発生しました", e);
                 }
                 updateStatusBar("村情報を読み直しました…");
@@ -1227,10 +1285,10 @@ public class Controller
                             }
                         });
                     }catch(InvocationTargetException e){
-                        Jindolf.logger().fatal(
+                        LOGGER.fatal(
                                 "ブラウザ表示で致命的な障害が発生しました", e);
                     }catch(InterruptedException e){
-                        Jindolf.logger().fatal(
+                        LOGGER.fatal(
                                 "ブラウザ表示で致命的な障害が発生しました", e);
                     }
                     EventQueue.invokeLater(new Runnable(){
@@ -1342,7 +1400,7 @@ public class Controller
      * @param e ネットワークエラー
      */
     public void showNetworkError(Land land, IOException e){
-        Jindolf.logger().warn("ネットワークで障害が発生しました", e);
+        LOGGER.warn("ネットワークで障害が発生しました", e);
 
         ServerAccess server = land.getServerAccess();
         String message =
@@ -1357,8 +1415,8 @@ public class Controller
                                            JOptionPane.WARNING_MESSAGE,
                                            JOptionPane.DEFAULT_OPTION );
 
-        JDialog dialog = pane.createDialog(this.topFrame,
-                                           "通信異常発生 - " + Jindolf.TITLE);
+        String title = VerInfo.getFrameTitle("通信異常発生");
+        JDialog dialog = pane.createDialog(this.topFrame, title);
 
         dialog.pack();
         dialog.setVisible(true);
@@ -1642,9 +1700,9 @@ public class Controller
             try{
                 SwingUtilities.invokeAndWait(microJob);
             }catch(InvocationTargetException e){
-                Jindolf.logger().fatal("ビジー処理で失敗", e);
+                LOGGER.fatal("ビジー処理で失敗", e);
             }catch(InterruptedException e){
-                Jindolf.logger().fatal("ビジー処理で失敗", e);
+                LOGGER.fatal("ビジー処理で失敗", e);
             }
         }
 
@@ -1664,15 +1722,9 @@ public class Controller
      * タイトルは指定された国or村名 + " - Jindolf"
      * @param name 国or村名
      */
-    private void setFrameTitle(CharSequence name){
-        String title = Jindolf.TITLE;
-
-        if(name != null && name.length() > 0){
-            title = name + " - " + title;
-        }
-
+    private void setFrameTitle(String name){
+        String title = VerInfo.getFrameTitle(name);
         this.topFrame.setTitle(title);
-
         return;
     }
 
@@ -1680,10 +1732,24 @@ public class Controller
      * アプリ正常終了処理。
      */
     private void shutdown(){
-        this.findPanel.saveHistory();
-        this.talkPreview.saveDraft();
-        Jindolf.getAppSetting().saveConfig();
-        Jindolf.exit(0);
+        ConfigStore configStore = this.appSetting.getConfigStore();
+
+        JsObject findConf = this.findPanel.getJson();
+        if( ! this.findPanel.hasConfChanged(findConf) ){
+            configStore.saveHistoryConfig(findConf);
+        }
+
+        JsObject draftConf = this.talkPreview.getJson();
+        if( ! this.talkPreview.hasConfChanged(draftConf) ){
+            configStore.saveDraftConfig(draftConf);
+        }
+
+        this.appSetting.saveConfig();
+
+        LOGGER.info("VMごとアプリケーションを終了します。");
+        System.exit(0);  // invoke shutdown hooks... BYE !
+
+        assert false;
         return;
     }
 
diff --git a/src/main/java/jp/sfjp/jindolf/Jindolf.java b/src/main/java/jp/sfjp/jindolf/Jindolf.java
new file mode 100644 (file)
index 0000000..ecfa30f
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Jindolf main class
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 olyutorskii
+ */
+
+package jp.sfjp.jindolf;
+
+/**
+ * Jindolf スタートアップクラス。
+ * <p>JRE実行系の互換性検査を主目的とする。
+ * <p>このクラスではJRE1.0互換なランタイムライブラリのみが利用できる。
+ * <p>このクラスはJRE1.0でもコンパイルできなければならない。
+ * アプリ開始はstaticメソッド{@link #main(String[])}呼び出しから。
+ */
+public final class Jindolf {
+
+    /**
+     * 隠しコンストラクタ。
+     */
+    private Jindolf(){
+        super();
+        return;
+    }
+
+    /**
+     * Jindolf のスタートアップエントリ。
+     * @param args コマンドライン引数
+     */
+    public static void main(String[] args){
+        JreChecker.checkJre();
+
+        JindolfJre15.main(args);
+
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sfjp/jindolf/JindolfGuiJp.java b/src/main/java/jp/sfjp/jindolf/JindolfGuiJp.java
new file mode 100644 (file)
index 0000000..e4c1f57
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Jindolf main class
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 olyutorskii
+ */
+
+package jp.sfjp.jindolf;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import javax.swing.JOptionPane;
+
+/**
+ * GUIとUnicode出力とリソースアクセスが解禁された
+ * Jindolf スタートアップクラス。
+ * <p>Jindolf_jre15の下請け。
+ */
+public final class JindolfGuiJp {
+
+    private static final String TITLE_INVOKEDBL = "多重起動";
+    private static final String MSG_INVOKEDBL =
+            "ERROR : 二度目以降の起動がキャンセルされました。";
+
+    /** 多重起動防止用セマフォ。 */
+    private static final AtomicBoolean INVOKE_FLAG =
+            new AtomicBoolean(false);
+
+
+    /**
+     * 隠しコンストラクタ。
+     */
+    private JindolfGuiJp(){
+        super();
+        assert false;
+        return;
+    }
+
+    /**
+     * JVM内で多重起動していないかチェックする。
+     * <p>多重起動を確認した場合は、GUIにダイアログを出力する。
+     * @return 多重起動していたらtrue
+     */
+    private static boolean hasInvokedCheck(){
+        boolean successed = INVOKE_FLAG.compareAndSet(false, true);
+        if(successed) return false;
+
+        JOptionPane.showMessageDialog(null,
+                                      MSG_INVOKEDBL,
+                                      TITLE_INVOKEDBL,
+                                      JOptionPane.ERROR_MESSAGE);
+
+        return true;
+    }
+
+    /**
+     * Jindolf のスタートアップエントリ。
+     * <p>ここからGUIとUnicode出力とリソースアクセスが解禁される。
+     * @param args コマンドライン引数
+     */
+    static void main(String... args){
+        if(hasInvokedCheck()) return;
+
+        JindolfOld.main(args);
+
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sfjp/jindolf/JindolfJre15.java b/src/main/java/jp/sfjp/jindolf/JindolfJre15.java
new file mode 100644 (file)
index 0000000..7e58b81
--- /dev/null
@@ -0,0 +1,171 @@
+/*
+ * Jindolf main class
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 olyutorskii
+ */
+
+package jp.sfjp.jindolf;
+
+import java.awt.GraphicsEnvironment;
+import javax.swing.JOptionPane;
+
+/**
+ * JRE1.5の利用が解禁されたJindolfエントリ。
+ * <p>起動クラスJindolfの下請けとしての機能が想定される。
+ * <p>必ずしも必要ないが、異常系切り分けに有用な、
+ * 実行環境やビルドの成否に関する各種診断を行う。
+ */
+public final class JindolfJre15 {
+
+    /** exit code. */
+    public static final int EXIT_CODE_HEADLESS = 1;
+    /** exit code. */
+    public static final int EXIT_CODE_BUILDENCO = 1;
+    /** exit code. */
+    public static final int EXIT_CODE_INVMANIFEST = 1;
+
+
+    /**
+     * 隠しコンストラクタ。
+     */
+    private JindolfJre15(){
+        super();
+        assert false;
+        return;
+    }
+
+
+    /**
+     * GUI環境の有無をチェックする。
+     * GUI環境に接続できなければJVMを終了させる。
+     */
+    private static void checkGuiEnv(){
+        if( ! GraphicsEnvironment.isHeadless() ) return;
+
+        String dispEnv;
+        try{
+            dispEnv = System.getenv("DISPLAY");
+        }catch(SecurityException e){
+            dispEnv = null;
+        }
+
+        StringBuilder message = new StringBuilder();
+
+        message.append("ERROR : GUI session failed.");
+
+        if(dispEnv != null){
+            message.append('\n')
+                   .append("Environment DISPLAY [")
+                   .append(dispEnv)
+                   .append("]");
+        }
+
+        System.err.println(message);
+        System.err.flush();
+
+        System.exit(EXIT_CODE_HEADLESS);
+
+        assert false;
+        return;
+    }
+
+    /**
+     * エラーダイアログの出力。
+     * @param errmsg エラーメッセージ
+     * @param title タイトル
+     */
+    private static void showErrorDialog(String errmsg, String title){
+        JOptionPane.showMessageDialog(null,
+                                      errmsg,
+                                      title,
+                                      JOptionPane.ERROR_MESSAGE);
+        return;
+    }
+
+    /**
+     * ビルド時のエンコーディングミスを判定する。
+     * ※ 非Unicode系の開発環境を使いたい人は適当に無視してね。
+     */
+    private static void checkBuildError(){
+        if(   '狼' == 0x72fc
+           && ' ' == 0x3000
+           && '~'  == 0x007e
+           && '\\' == 0x005c  // バックスラッシュ
+           && '¥'  == 0x00a5  // 半角円通貨
+           && '~' == 0xff5e
+           && '�' == 0xfffd  // Unicode専用特殊文字
+        ){
+            return;
+        }
+
+        String errmsg =
+                "ERROR : Invalid source-code encoding detected.\n"
+                +"Let's check encoding with compiler & source-file.";
+
+        showErrorDialog(errmsg, "Build failed");
+
+        System.exit(EXIT_CODE_BUILDENCO);
+
+        assert false;
+        return;
+    }
+
+    /**
+     * MANIFEST.MFパッケージ定義エラーの検出。
+     */
+    private static void checkPackageDefinition(){
+        Package rootPkg = ResourceManager.DEF_ROOT_PACKAGE;
+        String implTitle   = rootPkg.getImplementationTitle();
+        String implVersion = rootPkg.getImplementationVersion();
+        String implVendor  = rootPkg.getImplementationVendor();
+
+        String title = "ビルドエラー";
+
+        if(implTitle != null && ! VerInfo.TITLE.equals(implTitle) ){
+            String errmsg = "パッケージ定義とタイトルが一致しません。"
+                    +"["+ implTitle +"]≠["+ VerInfo.TITLE +"]";
+            showErrorDialog(errmsg, title);
+            System.exit(EXIT_CODE_INVMANIFEST);
+            assert false;
+        }
+
+        if(implVersion != null && ! VerInfo.VERSION.equals(implVersion)  ){
+            String errmsg = "パッケージ定義とバージョン番号が一致しません。"
+                    +"["+ implVersion +"]≠["+ VerInfo.VERSION +"]";
+            showErrorDialog(errmsg, title);
+            System.exit(EXIT_CODE_INVMANIFEST);
+            assert false;
+        }
+
+        if(implVendor != null && ! VerInfo.AUTHOR.equals(implVendor) ){
+            String errmsg = "パッケージ定義とベンダが一致しません。"
+                    +"["+ implVendor +"]≠["+ VerInfo.AUTHOR +"]";
+            showErrorDialog(errmsg, title);
+            System.exit(EXIT_CODE_INVMANIFEST);
+            assert false;
+        }
+
+        return;
+    }
+
+    /**
+     * Jindolf のスタートアップエントリ。
+     * <p>ここからJRE1.5の利用が解禁される。
+     * @param args コマンドライン引数
+     */
+    static void main(String... args){
+        checkGuiEnv();
+        // ここから異常系でのSwingGUI解禁
+
+        checkBuildError();
+        // ここからUnicode出力解禁。
+
+        checkPackageDefinition();
+
+        JindolfGuiJp.main(args);
+
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sfjp/jindolf/JindolfOld.java b/src/main/java/jp/sfjp/jindolf/JindolfOld.java
new file mode 100644 (file)
index 0000000..f0b1cf8
--- /dev/null
@@ -0,0 +1,452 @@
+/*
+ * Jindolf main class
+ *
+ * License : The MIT License
+ * Copyright(c) 2008 olyutorskii
+ */
+
+package jp.sfjp.jindolf;
+
+import java.awt.Dimension;
+import java.awt.EventQueue;
+import java.awt.Window;
+import java.lang.reflect.InvocationTargetException;
+import java.text.DateFormat;
+import java.text.NumberFormat;
+import java.util.Date;
+import javax.swing.ImageIcon;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JWindow;
+import javax.swing.UIManager;
+import jp.sfjp.jindolf.config.AppSetting;
+import jp.sfjp.jindolf.config.CmdOption;
+import jp.sfjp.jindolf.config.ConfigFile;
+import jp.sfjp.jindolf.config.ConfigStore;
+import jp.sfjp.jindolf.config.EnvInfo;
+import jp.sfjp.jindolf.config.OptionInfo;
+import jp.sfjp.jindolf.data.LandsModel;
+import jp.sfjp.jindolf.glyph.Discussion;
+import jp.sfjp.jindolf.glyph.GlyphDraw;
+import jp.sfjp.jindolf.log.LogUtils;
+import jp.sfjp.jindolf.log.LogWrapper;
+import jp.sfjp.jindolf.log.LoggingDispatcher;
+import jp.sfjp.jindolf.util.GUIUtils;
+import jp.sfjp.jindolf.view.ActionManager;
+import jp.sfjp.jindolf.view.TabBrowser;
+import jp.sfjp.jindolf.view.TopView;
+
+/**
+ * Jindolf スタートアップクラス。
+ *
+ * コンストラクタは無いよ。
+ * アプリ開始はstaticメソッド{@link #main(String[])}呼び出しから。
+ */
+public final class JindolfOld {
+
+    /** このClass。 */
+    public static final Class<?> SELF_KLASS;
+    /** クラスローダ。 */
+    public static final ClassLoader LOADER;
+
+
+    /** クラスロード時のナノカウント。 */
+    public static final long NANOCT_LOADED;
+    /** クラスロード時刻(エポックmsec)。 */
+    public static final long EPOCHMS_LOADED;
+
+
+    /** ロガー。 */
+    private static final LogWrapper LOGGER = new LogWrapper();
+
+    /** スプラッシュロゴ。 */
+    private static final String RES_LOGOICON =
+            "resources/image/logo.png";
+
+    static{
+        SELF_KLASS = JindolfOld.class;
+
+        ClassLoader thisLoader;
+        try{
+            thisLoader = SELF_KLASS.getClassLoader();
+        }catch(SecurityException e){
+            thisLoader = null;
+        }
+        LOADER = thisLoader;
+
+        NANOCT_LOADED  = System.nanoTime();
+        EPOCHMS_LOADED = System.currentTimeMillis();
+
+        new JindolfOld().hashCode();
+    }
+
+
+    /**
+     * 隠れコンストラクタ。
+     */
+    private JindolfOld(){
+        super();
+        assert this.getClass() == SELF_KLASS;
+        return;
+    }
+
+
+    /**
+     * 標準出力端末にヘルプメッセージ(オプションの説明)を表示する。
+     */
+    private static void showHelpMessage(){
+        System.out.flush();
+        System.err.flush();
+
+        CharSequence helpText = CmdOption.getHelpText();
+        System.out.print(helpText);
+
+        System.out.flush();
+        System.err.flush();
+
+        return;
+    }
+
+    /**
+     * スプラッシュウィンドウを表示する。
+     * <p>JRE1.6以降では何も表示しない。
+     * @return スプラッシュウィンドウ。JRE1.6以降ならnullを返す。
+     */
+    @SuppressWarnings("CallToThreadYield")
+    private static Window showSplash(){
+        if(JreChecker.has16Runtime()) return null;
+
+        Window splashWindow = new JWindow();
+
+        ImageIcon logo = ResourceManager.getImageIcon(RES_LOGOICON);
+        JLabel splashLabel = new JLabel(logo);
+        splashWindow.add(splashLabel);
+
+        splashWindow.pack();
+        splashWindow.setLocationRelativeTo(null); // locate center
+        splashWindow.setVisible(true);
+
+        Thread.yield();
+
+        return splashWindow;
+    }
+
+    /**
+     * スプラッシュウィンドウを隠す。
+     * @param splashWindow スプラッシュウィンドウ。nullならなにもしない。
+     */
+    @SuppressWarnings("CallToThreadYield")
+    private static void hideSplash(Window splashWindow){
+        if(splashWindow == null) return;
+
+        splashWindow.setVisible(false);
+        splashWindow.dispose();
+
+        Thread.yield();
+
+        return;
+    }
+
+    /**
+     * 起動時の諸々の情報をログ出力する。
+     * @param optinfo コマンドライン情報
+     * @param configStore 設定ディレクトリ情報
+     */
+    private static void dumpBootInfo(OptionInfo optinfo,
+                                       ConfigStore configStore ){
+        DateFormat dform = DateFormat.getDateTimeInstance();
+        NumberFormat nform = NumberFormat.getNumberInstance();
+
+        LOGGER.info(
+                VerInfo.ID + " は "
+                + dform.format(new Date(EPOCHMS_LOADED))
+                + " にVM上のクラス "
+                + SELF_KLASS.getName() + " としてロードされました。 " );
+
+        LOGGER.info("Initial Nano-Count : " + nform.format(NANOCT_LOADED));
+
+        Runtime runtime = Runtime.getRuntime();
+        LOGGER.info(
+                "Max-heap : "
+                + nform.format(runtime.maxMemory()) + " Byte"
+                + "   Total-heap : "
+                + nform.format(runtime.totalMemory()) + " Byte");
+
+        StringBuilder bootArgs = new StringBuilder();
+        bootArgs.append("\n\n").append("起動時引数:\n");
+        for(String arg : optinfo.getInvokeArgList()){
+            bootArgs.append("\u0020\u0020").append(arg).append('\n');
+        }
+        bootArgs.append('\n');
+        bootArgs.append(EnvInfo.getVMInfo());
+        LOGGER.info(bootArgs);
+
+        if(configStore.useStoreFile()){
+            LOGGER.info("設定格納ディレクトリに[ "
+                    + configStore.getConfigPath().getPath()
+                    + " ]が指定されました。");
+        }else{
+            LOGGER.info("設定格納ディレクトリは使いません。");
+        }
+
+        if(   JreChecker.has16Runtime()
+           && optinfo.hasOption(CmdOption.OPT_NOSPLASH) ){
+            LOGGER.warn(
+                      "JRE1.6以降では、"
+                    +"Jindolfの-nosplashオプションは無効です。"
+                    + "Java実行系の方でスプラッシュ画面の非表示を"
+                    + "指示してください(おそらく空の-splash:オプション)" );
+        }
+
+        if(LOADER == null){
+            LOGGER.warn(
+                    "セキュリティ設定により、"
+                    +"クラスローダを取得できませんでした");
+        }
+
+        return;
+    }
+
+    /**
+     * 任意のクラス群に対して一括ロード/初期化を単一スレッドで順に行う。
+     * どーしてもクラス初期化の順序に依存する障害が発生する場合や
+     * クラス初期化のオーバーヘッドでGUIの操作性が損なわれるときなどにどうぞ。
+     *
+     * @throws java.lang.LinkageError クラス間リンケージエラー。
+     * @throws java.lang.ExceptionInInitializerError クラス初期化で異常
+     */
+    private static void preInitClass()
+            throws LinkageError,
+                   ExceptionInInitializerError {
+        Object[] classes = {            // Class型 または String型
+            "java.lang.Object",
+            TabBrowser.class,
+            Discussion.class,
+            GlyphDraw.class,
+            java.net.HttpURLConnection.class,
+            java.text.SimpleDateFormat.class,
+            Void.class,
+        };
+
+        for(Object obj : classes){
+            String className;
+            if(obj instanceof Class){
+                className = ((Class<?>)obj).getName();
+            }else if(obj instanceof String){
+                className = obj.toString();
+            }else{
+                continue;
+            }
+
+            try{
+                if(LOADER != null){
+                    Class.forName(className, true, LOADER);
+                }else{
+                    Class.forName(className);
+                }
+            }catch(ClassNotFoundException e){
+                LOGGER.warn("クラスの明示的ロードに失敗しました", e);
+                continue;
+            }
+        }
+
+        return;
+    }
+
+    /**
+     * JindolfOld のスタートアップエントリ。
+     *
+     * @param args コマンドライン引数
+     */
+    static void main(String... args){
+        OptionInfo optinfo;
+
+        try{
+            optinfo = OptionInfo.parseOptions(args);
+        }catch(IllegalArgumentException e){
+            String message = e.getLocalizedMessage();
+            System.err.println(message);
+            System.err.println(
+                "起動オプション一覧は、"
+                + "起動オプションに「"
+                + CmdOption.OPT_HELP.toString()
+                + "」を指定すると確認できます。" );
+            System.exit(1);
+            assert false;
+            return;
+        }
+
+        main(optinfo);
+
+        return;
+    }
+
+    /**
+     * JindolfOld のスタートアップエントリ。
+     *
+     * @param optinfo コマンドライン引数情報
+     */
+    static void main(OptionInfo optinfo){
+        if(optinfo.hasOption(CmdOption.OPT_HELP)){
+            showHelpMessage();
+            System.exit(0);
+            assert false;
+            return;
+        }
+
+        if(optinfo.hasOption(CmdOption.OPT_VERSION)){
+            System.out.println(VerInfo.ID);
+            System.exit(0);
+            assert false;
+            return;
+        }
+
+        // ここ以降、アプリウィンドウの生成と表示に向けてまっしぐら。
+
+        // あらゆるSwing文字列表示処理より前に必要。
+        // システムプロパティ swing.boldMetal は無視される。
+        Boolean boldFlag;
+        if(optinfo.hasOption(CmdOption.OPT_BOLDMETAL)){
+            // もの凄く日本語表示が汚くなるかもよ!注意
+            boldFlag = Boolean.TRUE;
+        }else{
+            boldFlag = Boolean.FALSE;
+        }
+        UIManager.put("swing.boldMetal", boldFlag);
+
+        // JRE1.5用スプラッシュウィンドウ
+        Window splashWindow = null;
+        if( ! optinfo.hasOption(CmdOption.OPT_NOSPLASH) ){
+            splashWindow = showSplash();
+        }
+
+        boolean hasError = false;
+        try{
+            hasError = splashedMain(optinfo);
+        }finally{
+            hideSplash(splashWindow);
+        }
+
+        if(hasError) System.exit(1);
+
+        return;
+    }
+
+    /**
+     * JindolfOld のスタートアップエントリ。
+     * <p>スプラッシュウィンドウが出ている状態。
+     * @param optinfo コマンドライン引数情報
+     * @return エラーがあればtrue
+     */
+    static boolean splashedMain(OptionInfo optinfo){
+        final AppSetting appSetting = new AppSetting();
+        appSetting.applyOptionInfo(optinfo);
+
+        if(optinfo.hasOption(CmdOption.OPT_VMINFO)){
+            System.out.println(EnvInfo.getVMInfo());
+        }
+
+        LogUtils.initRootLogger(optinfo.hasOption(CmdOption.OPT_CONSOLELOG));
+        // ここからロギング解禁
+
+        ConfigStore configStore = appSetting.getConfigStore();
+        dumpBootInfo(optinfo, configStore);
+
+        ConfigFile.setupConfigDirectory(configStore);
+        ConfigFile.setupLockFile(configStore);
+        // ここから設定格納ディレクトリ解禁
+
+        appSetting.loadConfig();
+
+        final Runtime runtime = Runtime.getRuntime();
+        runtime.addShutdownHook(new Thread(){
+            @Override
+            @SuppressWarnings("CallToThreadYield")
+            public void run(){
+                LOGGER.info("シャットダウン処理に入ります…");
+                System.out.flush();
+                System.err.flush();
+                runtime.gc();
+                Thread.yield();
+                runtime.runFinalization(); // 危険?
+                Thread.yield();
+                return;
+            }
+        });
+
+        preInitClass();
+
+        LoggingDispatcher.replaceEventQueue();
+
+        boolean hasError = false;
+        try{
+            EventQueue.invokeAndWait(new Runnable(){
+                public void run(){
+                    startGUI(appSetting);
+                    return;
+                }
+            });
+        }catch(InvocationTargetException e){
+            LOGGER.fatal("アプリケーション初期化に失敗しました", e);
+            e.printStackTrace(System.err);
+            hasError = true;
+        }catch(InterruptedException e){
+            LOGGER.fatal("アプリケーション初期化に失敗しました", e);
+            e.printStackTrace(System.err);
+            hasError = true;
+        }
+
+        return hasError;
+    }
+
+    /**
+     * AWTイベントディスパッチスレッド版スタートアップエントリ。
+     * @param appSetting アプリ設定
+     */
+    private static void startGUI(AppSetting appSetting){
+        LandsModel model = new LandsModel();
+        model.loadLandList();
+
+        JFrame topFrame = buildMVC(appSetting, model);
+
+        GUIUtils.modifyWindowAttributes(topFrame, true, false, true);
+
+        topFrame.pack();
+
+        Dimension initGeometry =
+                new Dimension(appSetting.initialFrameWidth(),
+                              appSetting.initialFrameHeight());
+        topFrame.setSize(initGeometry);
+
+        if(   appSetting.initialFrameXpos() <= Integer.MIN_VALUE
+           || appSetting.initialFrameYpos() <= Integer.MIN_VALUE ){
+            topFrame.setLocationByPlatform(true);
+        }else{
+            topFrame.setLocation(appSetting.initialFrameXpos(),
+                                 appSetting.initialFrameYpos() );
+        }
+
+        topFrame.setVisible(true);
+
+        return;
+    }
+
+    /**
+     * モデル・ビュー・コントローラの結合。
+     * @param appSetting アプリ設定
+     * @param model 最上位のデータモデル
+     * @return アプリケーションのトップフレーム
+     */
+    private static JFrame buildMVC(AppSetting appSetting, LandsModel model){
+        ActionManager actionManager = new ActionManager();
+        TopView topView = new TopView();
+
+        Controller controller =
+                new Controller(appSetting, actionManager, topView, model);
+
+        JFrame topFrame = controller.createTopFrame();
+
+        return topFrame;
+    }
+
+}
diff --git a/src/main/java/jp/sfjp/jindolf/JreChecker.java b/src/main/java/jp/sfjp/jindolf/JreChecker.java
new file mode 100644 (file)
index 0000000..a23ea9f
--- /dev/null
@@ -0,0 +1,277 @@
+/*
+ * JRE checker
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 olyutorskii
+ */
+
+package jp.sfjp.jindolf;
+
+import javax.swing.JOptionPane;
+
+/**
+ * JRE互換性のチェックを行う。
+ * <p>JREのバージョンと、
+ * JREに含まれるjava.langパッケージ仕様のバージョンは互換性があるものとする。
+ * <p>なるべく昔のJREでも動くように。
+ * 少なくとも1.2以降。できれば1.0以降。
+ * 原則、1.3以降の新設ライブラリは利用禁止。
+ * <p>なるべく昔のコンパイラでもコンパイルできるように。
+ * 少なくとも1.2以降。できれば1.0以降。
+ * 原則、assert文、総称ジェネリクス、アノテーションは禁止。
+ * <p>できればBasic-Latinに入らない文字(日本語)の出力も禁止。
+ * <p>ランタイム存在検査用のクラスは、ロードや初期化が軽いほうが望ましい。
+ * そしていずれJindolfの実行に必要なものであるのが望ましい。
+ * もしそうでない場合でも、
+ * Jindolfに関係ないクラスの連鎖ロードが起きにくいほうが望ましい。
+ */
+public final class JreChecker {
+
+    /** required JRE version. */
+    public static final String REQUIRED_JRE_VER = "1.5";
+
+    /** exit code. */
+    public static final int EXIT_CODE_INCOMPAT_JRE = 1;
+
+    private static final String DIALOG_TITLE =
+            "JRE Incompatibility detected...";
+
+    private static final int MAX_LINE = 40;
+
+
+    /**
+     * 隠しコンストラクタ。
+     */
+    private JreChecker(){
+        super();
+        return;
+    }
+
+
+    /**
+     * クラス名に相当するクラスがロードできるか判定する。
+     * @param klassName FQDNなクラス名
+     * @return ロードできたらtrue
+     */
+    private static boolean hasClass(String klassName){
+        boolean result = false;
+
+        try{
+            Class.forName(klassName); // 1.2Laterな3引数版メソッドは利用禁止
+            result = true;
+        }catch(ClassNotFoundException e){
+            result = false;
+        }catch(LinkageError e){
+            throw e;
+        }
+
+        return result;
+    }
+
+    /**
+     * JRE 1.1 相当のランタイムライブラリが提供されているか判定する。
+     * @return 提供されているならtrue
+     */
+    public static boolean has11Runtime(){
+        boolean result = hasClass("java.io.Serializable");
+        return result;
+    }
+
+    /**
+     * JRE 1.2 相当のランタイムライブラリが提供されているか判定する。
+     * @return 提供されているならtrue
+     */
+    public static boolean has12Runtime(){
+        boolean result;
+        if(has11Runtime()) result = hasClass("java.util.Iterator");
+        else               result = false;
+        return result;
+    }
+
+    /**
+     * JRE 1.3 相当のランタイムライブラリが提供されているか判定する。
+     * @return 提供されているならtrue
+     */
+    public static boolean has13Runtime(){
+        boolean result;
+        if(has12Runtime()) result = hasClass("java.util.TimerTask");
+        else               result = false;
+        return result;
+    }
+
+    /**
+     * JRE 1.4 相当のランタイムライブラリが提供されているか判定する。
+     * @return 提供されているならtrue
+     */
+    public static boolean has14Runtime(){
+        boolean result;
+        if(has13Runtime()) result = hasClass("java.lang.CharSequence");
+        else               result = false;
+        return result;
+    }
+
+    /**
+     * JRE 1.5 相当のランタイムライブラリが提供されているか判定する。
+     * @return 提供されているならtrue
+     */
+    public static boolean has15Runtime(){
+        boolean result;
+        if(has14Runtime()) result = hasClass("java.lang.Appendable");
+        else               result = false;
+        return result;
+    }
+
+    /**
+     * JRE 1.6 相当のランタイムライブラリが提供されているか判定する。
+     * @return 提供されているならtrue
+     */
+    public static boolean has16Runtime(){
+        boolean result;
+        if(has15Runtime()) result = hasClass("java.util.Deque");
+        else               result = false;
+        return result;
+    }
+
+    /**
+     * JREもしくはjava.langパッケージの仕様バージョンを返す。
+     * @return 仕様バージョン文字列。不明ならnull
+     */
+    public static String getLangPkgSpec(){
+        String result;
+
+        try{
+            result = System.getProperty("java.specification.version");
+        }catch(SecurityException e){
+            result = null;
+        }
+        if(result != null) return result;
+
+        try{
+            result = System.getProperty("java.version");
+        }catch(SecurityException e){
+            result = null;
+        }
+        if(result != null) return result;
+
+        Package javaLangPkg = java.lang.Object.class.getPackage();
+        if(javaLangPkg == null) return null;
+
+        result = javaLangPkg.getSpecificationVersion();
+        return result;
+    }
+
+    /**
+     * JREのインストール情報を返す。
+     * @return インストール情報。不明ならnull
+     */
+    public static String getJreHome(){
+        String result;
+
+        try{
+            result = System.getProperty("java.home");
+        }catch(SecurityException e){
+            result = null;
+        }
+        if(result != null) return result;
+
+        return result;
+    }
+
+    /**
+     * 非互換エラーメッセージを組み立てる。
+     * @return エラーメッセージ
+     */
+    public static String buildErrMessage(){
+        StringBuffer message = new StringBuffer();
+
+        message.append("ERROR : Java JRE ")
+               .append(REQUIRED_JRE_VER)
+               .append(" compatible or later required.");
+
+        String specVer = getLangPkgSpec();
+        if(specVer != null){
+            message.append('\n')
+                   .append("but").append('\u0020')
+                   .append(specVer)
+                   .append('\u0020').append("detected.");
+        }
+
+        String jreHome = getJreHome();
+        if(jreHome != null){
+            message.append("  [ ")
+                   .append(jreHome)
+                   .append(" ]");
+        }
+
+        return message.toString();
+    }
+
+    /**
+     * 指定された文字数で行の長さを揃える。
+     * <p>サロゲートペアは無視される。
+     * @param text 文字列
+     * @param limit 行ごとの最大文字数
+     * @return 改行済みの文字列
+     */
+    public static String alignLine(String text, int limit){
+        StringBuffer message = new StringBuffer();
+
+        int lineLength = 0;
+
+        int textLength = text.length();
+        for(int idx = 0; idx < textLength; idx++){
+            if(lineLength >= limit){
+                message.append('\n');
+                lineLength = 0;
+            }
+
+            char ch = text.charAt(idx);
+            message.append(ch);
+
+            if(ch == '\n') lineLength = 0;
+            else           lineLength++;
+        }
+
+        String result = message.toString();
+        return result;
+    }
+
+    /**
+     * Swingダイアログでエラーを報告する。
+     * <p>ボタンを押すまでの間、実行はブロックされる。
+     * <p>JRE1.2環境が用意されていなければ何もしない。
+     * <p>GUIに接続できなければ何か例外を投げるかもしれない。
+     * @param text エラー文面
+     */
+    public static void showErrorDialog(String text){
+        if( ! has12Runtime() ) return;
+        String aligned = alignLine(text, MAX_LINE);
+        JOptionPane.showMessageDialog(
+                null,
+                aligned, DIALOG_TITLE,
+                JOptionPane.ERROR_MESSAGE );
+        return;
+    }
+
+    /**
+     * JRE環境をチェックする。(JRE1.5)
+     * <p>もしJREの非互換性が検出されたらエラーメッセージを報告し、
+     * 所定の終了コードでJVMを終了させる。
+     */
+    public static void checkJre(){
+        if(has15Runtime()) return;
+
+        // 以降、JVM終了へ向けて一直線。
+        try{
+            String message = buildErrMessage();
+            System.err.println(message);
+            System.err.flush();
+            showErrorDialog(message);
+        }finally{
+            System.exit(EXIT_CODE_INCOMPAT_JRE);
+        }
+
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sfjp/jindolf/ResourceManager.java b/src/main/java/jp/sfjp/jindolf/ResourceManager.java
new file mode 100644 (file)
index 0000000..e9d3144
--- /dev/null
@@ -0,0 +1,303 @@
+/*
+ * resource manager
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 olyutorskii
+ */
+
+package jp.sfjp.jindolf;
+
+import java.awt.image.BufferedImage;
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.io.Reader;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.util.Properties;
+import javax.imageio.ImageIO;
+import javax.swing.ImageIcon;
+
+/**
+ * 各種リソースファイルの管理。
+ * <p>{@link Class}用と{@link ClassLoader}用とでは
+ * 微妙に絶対リソース名の形式が異なることに留意せよ。
+ * <p>基本的に、リソースファイルへのアクセスにおける異常系は
+ * リカバリの対象外とする。ビルド工程の不手際扱い。
+ * @see java.lang.Class#getResource
+ */
+public final class ResourceManager {
+
+    /** リソース名セパレータ文字。 */
+    public static final char RES_SEPCHAR = '/';
+    /** パッケージ名セパレータ文字。 */
+    public static final char PKG_SEPCHAR = '.';
+
+    /** リソース名セパレータ文字列。 */
+    public static final String RES_SEPARATOR =
+            Character.toString(RES_SEPCHAR);
+    /** パッケージ名セパレータ文字列。 */
+    public static final String PKG_SEPARATOR =
+            Character.toString(PKG_SEPCHAR);
+
+    /** デフォルトで用いられるルートパッケージ。 */
+    public static final Package DEF_ROOT_PACKAGE;
+    /** デフォルトで用いられるクラスローダ。 */
+    public static final ClassLoader DEF_LOADER;
+
+    private static final Charset CS_UTF8 = Charset.forName("UTF-8");
+
+    static{
+        Class<?> rootKlass = Jindolf.class;
+
+        DEF_ROOT_PACKAGE = rootKlass.getPackage();
+        DEF_LOADER = rootKlass.getClassLoader();
+    }
+
+
+    /**
+     * 隠しコンストラクタ。
+     */
+    private ResourceManager(){
+        super();
+        assert false;
+        return;
+    }
+
+
+    /**
+     * リソース名が絶対パスか否か判定する。
+     * <p>リソース名が「/」で始まる場合絶対パスとみなされる。
+     * <p>このリソース名は{@link Class}用であって
+     * {@link ClassLoader}用ではない。
+     * @param resPath リソース名
+     * @return 絶対パスならtrueを返す。
+     * @see java.lang.Class#getResource
+     */
+    public static boolean isAbsoluteResourcePath(String resPath){
+        if (resPath.startsWith(RES_SEPARATOR)) return true;
+        return false;
+    }
+
+    /**
+     * パッケージ情報を反映するリソース名前置詞を返す。
+     * <p>パッケージ名のセパレータ「.」は「/」に置き換えられる。
+     * 無名パッケージの場合は長さゼロの空文字列を返す。
+     * 無名パッケージを除き、リソース名前置詞は必ず「/」で終わる。
+     * <p>この前置詞は{@link ClassLoader}用であって
+     * {@link Class}用ではないので、
+     * 頭に「/」が付かない。
+     * @param pkg パッケージ設定。nullは無名パッケージと認識される。
+     * @return リソース名前置詞。無名パッケージの場合は空文字列が返る。
+     * @see java.lang.Class#getResource
+     */
+    public static String getResourcePrefix(Package pkg){
+        if(pkg == null) return "";   // 無名パッケージ
+
+        String pkgName = pkg.getName();
+        String result = pkgName.replace(PKG_SEPCHAR, RES_SEPCHAR);
+        if( ! result.isEmpty() ){
+            result += RES_SEPARATOR;
+        }
+
+        return result;
+    }
+
+    /**
+     * リソース名を用いて、
+     * デフォルトのクラスローダとデフォルトのルートパッケージから
+     * リソースのURLを取得する。
+     * @param resPath リソース名
+     * @return リソースのURL。リソースが見つからなければnull。
+     */
+    public static URL getResource(String resPath){
+        return getResource(DEF_ROOT_PACKAGE, resPath);
+    }
+
+    /**
+     * 任意のルートパッケージと相対リソース名を用いて、
+     * デフォルトのクラスローダからリソースのURLを取得する。
+     * @param rootPkg ルートパッケージ情報。
+     * 「/」で始まる絶対リソース名が指定された場合は無視される。
+     * @param resPath リソース名
+     * @return リソースのURL。リソースが見つからなければnull。
+     */
+    public static URL getResource(Package rootPkg, String resPath){
+        return getResource(DEF_LOADER, rootPkg, resPath);
+    }
+
+    /**
+     * 任意のルートパッケージと相対リソース名を用いて、
+     * 任意のクラスローダからリソースのURLを取得する。
+     * @param loader クラスローダ
+     * @param rootPkg ルートパッケージ情報。
+     * 「/」で始まる絶対リソース名が指定された場合は無視される。
+     * @param resPath リソース名
+     * @return リソースのURL。リソースが見つからなければnull。
+     */
+    public static URL getResource(ClassLoader loader,
+                                  Package rootPkg,
+                                  String resPath ){
+        String fullName;
+        if(isAbsoluteResourcePath(resPath)){
+            fullName = resPath.substring(1);    // chop '/' heading
+        }else{
+            String pfx = getResourcePrefix(rootPkg);
+            fullName = pfx + resPath;
+        }
+
+        URL result = loader.getResource(fullName);
+
+        return result;
+    }
+
+    /**
+     * リソース名を用いて、
+     * デフォルトのクラスローダとデフォルトのルートパッケージから
+     * リソースの入力ストリームを取得する。
+     * @param resPath リソース名
+     * @return リソースの入力ストリーム。リソースが見つからなければnull。
+     */
+    public static InputStream getResourceAsStream(String resPath){
+        return getResourceAsStream(DEF_ROOT_PACKAGE, resPath);
+    }
+
+    /**
+     * 任意のルートパッケージと相対リソース名を用いて、
+     * デフォルトのクラスローダからリソースの入力ストリームを取得する。
+     * @param rootPkg ルートパッケージ情報。
+     * 「/」で始まる絶対リソース名が指定された場合は無視される。
+     * @param resPath リソース名
+     * @return リソースの入力ストリーム。リソースが見つからなければnull。
+     */
+    public static InputStream getResourceAsStream(Package rootPkg,
+                                                  String resPath ){
+        return getResourceAsStream(DEF_LOADER, rootPkg, resPath);
+    }
+
+    /**
+     * 任意のルートパッケージと相対リソース名を用いて、
+     * 任意のクラスローダからリソースの入力ストリームを取得する。
+     * @param loader クラスローダ
+     * @param rootPkg ルートパッケージ情報。
+     * 「/」で始まる絶対リソース名が指定された場合は無視される。
+     * @param resPath リソース名
+     * @return リソースの入力ストリーム。リソースが見つからなければnull。
+     */
+    public static InputStream getResourceAsStream(ClassLoader loader,
+                                                  Package rootPkg,
+                                                  String resPath ){
+        URL url = getResource(loader, rootPkg, resPath);
+        if(url == null) return null;
+
+        InputStream result;
+        try{
+            result = url.openStream();
+        }catch (IOException e){
+            result = null;
+        }
+
+        return result;
+    }
+
+    /**
+     * リソース名を用いてイメージ画像を取得する。
+     * @param resPath 画像リソース名
+     * @return イメージ画像。リソースが見つからなければnull。
+     */
+    public static BufferedImage getBufferedImage(String resPath){
+        URL url = getResource(resPath);
+        if(url == null) return null;
+
+        BufferedImage result;
+        try{
+            result = ImageIO.read(url);
+        }catch(IOException e){
+            result = null;
+        }
+
+        return result;
+    }
+
+    /**
+     * リソース名を用いてアイコン画像を取得する。
+     * @param resPath アイコン画像リソース名
+     * @return アイコン画像。リソースが見つからなければnull。
+     */
+    public static ImageIcon getImageIcon(String resPath){
+        URL url = getResource(resPath);
+        if(url == null) return null;
+
+        ImageIcon result = new ImageIcon(url);
+
+        return result;
+    }
+
+    /**
+     * リソース名を用いてプロパティを取得する。
+     * @param resPath プロパティファイルのリソース名
+     * @return プロパティ。リソースが読み込めなければnull。
+     */
+    public static Properties getProperties(String resPath){
+        InputStream is = getResourceAsStream(resPath);
+        if(is == null) return null;
+        is = new BufferedInputStream(is);
+
+        Properties properties = new Properties();
+
+        try{
+            properties.load(is);
+        }catch(IOException e){
+            properties = null;
+        }
+
+        try{
+            is.close();
+        }catch(IOException e){
+            properties = null;
+        }
+
+        return properties;
+    }
+
+    /**
+     * リソース名を用いてUTF-8テキストファイルの内容を取得する。
+     * <p>「#」で始まる行はコメント行として無視される。
+     * @param resPath テキストファイルのリソース名
+     * @return テキスト。リソースが読み込めなければnull。
+     */
+    public static CharSequence getTextFile(String resPath){
+        InputStream is = getResourceAsStream(resPath);
+        if(is == null) return null;
+        is = new BufferedInputStream(is);
+
+        Reader reader = new InputStreamReader(is, CS_UTF8);
+        LineNumberReader lineReader = new LineNumberReader(reader);
+
+        StringBuilder result = new StringBuilder();
+
+        for(;;){
+            String line;
+            try{
+                line = lineReader.readLine();
+            }catch(IOException e){
+                result = null;
+                break;
+            }
+            if(line == null) break;
+            if(line.startsWith("#")) continue;
+            result.append(line).append('\n');
+        }
+
+        try{
+            lineReader.close();
+        }catch(IOException e){
+            result = null;
+        }
+
+        return result;
+    }
+
+}
diff --git a/src/main/java/jp/sfjp/jindolf/VerInfo.java b/src/main/java/jp/sfjp/jindolf/VerInfo.java
new file mode 100644 (file)
index 0000000..7abd857
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+ * version information
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 olyutorskii
+ */
+
+package jp.sfjp.jindolf;
+
+import java.util.Properties;
+
+/**
+ * バージョンその他アプリに関する各種情報。
+ */
+public final class VerInfo {
+
+    /** タイトル。 */
+    public static final String TITLE;
+    /** バージョン。 */
+    public static final String VERSION;
+    /** 作者名。 */
+    public static final String AUTHOR;
+    /** 著作権表記。 */
+    public static final String COPYRIGHT;
+    /** ライセンス表記。 */
+    public static final String LICENSE;
+    /** 連絡先。 */
+    public static final String CONTACT;
+    /** 初出。 */
+    public static final String INCEPTION;
+    /** その他、何でも書きたいこと。 */
+    public static final String COMMENT;
+    /** クレジット。 */
+    public static final String ID;
+
+    private static final String RES_VERDEF = "resources/version.properties";
+
+    private static final String PFX_TITLE     = "pkg-title.";
+    private static final String PFX_VERSION   = "pkg-version.";
+    private static final String PFX_AUTHOR    = "pkg-author.";
+    private static final String PFX_LICENSE   = "pkg-license.";
+    private static final String PFX_CONTACT   = "pkg-contact.";
+    private static final String PFX_INCEPTION = "pkg-inception.";
+    private static final String PFX_COMMENT   = "pkg-comment.";
+
+    static{
+        Properties verProp = ResourceManager.getProperties(RES_VERDEF);
+        if(verProp == null) verProp = new Properties();
+
+        TITLE     = getPackageInfo(verProp, PFX_TITLE,     "Jindolf");
+        VERSION   = getPackageInfo(verProp, PFX_VERSION,   "0.0.1");
+        AUTHOR    = getPackageInfo(verProp, PFX_AUTHOR,    "nobody");
+        LICENSE   = getPackageInfo(verProp, PFX_LICENSE,   "Unknown");
+        CONTACT   = getPackageInfo(verProp, PFX_CONTACT,   "Unknown");
+        INCEPTION = getPackageInfo(verProp, PFX_INCEPTION, "2008");
+        COMMENT   = getPackageInfo(verProp, PFX_COMMENT,   "");
+
+        COPYRIGHT = "Copyright(c)" +"\u0020"+ INCEPTION +"\u0020"+ AUTHOR;
+
+        ID = TITLE
+            +"\u0020"+ "Ver." + VERSION
+            +"\u0020"+ COPYRIGHT
+            +"\u0020"+ "("+ LICENSE +")";
+    }
+
+    /**
+     * 隠しコンストラクタ。
+     */
+    private VerInfo(){
+        super();
+        assert false;
+        return;
+    }
+
+
+    /**
+     * プロパティからルートパッケージのパッケージ情報を取得する。
+     * @param prop プロパティ
+     * @param prefix 接頭辞
+     * @param defValue 見つからなかった場合のデフォルト値
+     * @return パッケージ情報
+     */
+    static String getPackageInfo(Properties prop,
+                                 String prefix,
+                                 String defValue ){
+        String result = getPackageInfo(prop, prefix,
+                                       ResourceManager.DEF_ROOT_PACKAGE,
+                                       defValue );
+        return result;
+    }
+
+    /**
+     * プロパティからパッケージに紐づけられたパッケージ情報を取得する。
+     * @param prop プロパティ
+     * @param prefix 接頭辞
+     * @param pkg 任意のパッケージ
+     * @param defValue 見つからなかった場合のデフォルト値
+     * @return パッケージ情報
+     */
+    static String getPackageInfo(Properties prop,
+                                 String prefix,
+                                 Package pkg,
+                                 String defValue ){
+        String propKeyName = prefix + pkg.getName();
+        String result = prop.getProperty(propKeyName, defValue);
+
+        // ignore Maven macro filtering
+        if(isMavenMacro(result)){
+            result = defValue;
+        }
+
+        return result;
+    }
+
+    /**
+     * 文字列がMavenのマクロフィルタ置換子でありうるか判定する。
+     * <p>「${」で始まり「}」で終わる文字列を置換子とみなす。
+     * <p>マクロ展開結果がさらに偶然引っかかった場合はあきらめる。
+     * @param text 文字列
+     * @return 置換子でありうるならtrue
+     */
+    public static boolean isMavenMacro(String text){
+        if(text.startsWith("$" + "{") && text.endsWith("}")){
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * ウィンドウタイトル名を生成する。
+     * <p>各ウィンドウタイトルには、他のアプリとの区別のため
+     * アプリ名が付加される。
+     * @param base タイトル基本部
+     * @return アプリ名が付加されたウィンドウタイトル。
+     */
+    public static String getFrameTitle(String base){
+        StringBuilder result = new StringBuilder();
+
+        if(base != null){
+            result.append(base).append("\u0020-\u0020");
+        }
+        result.append(TITLE);
+
+        String message = result.toString();
+        return message;
+    }
+
+    /**
+     * About画面用メッセージを生成する。
+     * @return About画面用メッセージ
+     */
+    public static String getAboutMessage(){
+        StringBuilder result = new StringBuilder();
+
+        result.append(TITLE)
+              .append("\u0020\u0020\u0020")
+              .append("Version")
+              .append('\u0020')
+              .append(VERSION)
+              .append('\n');
+        result.append(COPYRIGHT)
+              .append('\n');
+        result.append("ライセンス:\u0020")
+              .append(LICENSE)
+              .append('\n');
+        result.append("連絡先:\u0020")
+              .append(CONTACT);
+
+        if(COMMENT.length() > 0){
+            result.append('\n')
+                  .append(COMMENT);
+        }
+
+        String message = result.toString();
+        return message;
+    }
+
+}
@@ -5,11 +5,13 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.config;
 
 import java.awt.Font;
-import java.awt.font.FontRenderContext;
 import java.io.File;
+import jp.sfjp.jindolf.data.DialogPref;
+import jp.sfjp.jindolf.glyph.FontInfo;
+import jp.sfjp.jindolf.net.ProxyInfo;
 import jp.sourceforge.jovsonz.JsBoolean;
 import jp.sourceforge.jovsonz.JsObject;
 import jp.sourceforge.jovsonz.JsPair;
@@ -20,10 +22,8 @@ import jp.sourceforge.jovsonz.JsValue;
  */
 public class AppSetting{
 
-    private static final String NETCONFIG_FILE = "netconfig.json";
     private static final String HASH_PROXY = "proxy";
 
-    private static final String TALKCONFIG_FILE = "talkconfig.json";
     private static final String HASH_FONT = "font";
     private static final String HASH_USEBODYICON = "useBodyIcon";
     private static final String HASH_USEMONOTOMB = "useMonoTomb";
@@ -31,10 +31,7 @@ public class AppSetting{
     private static final String HASH_ALIGNBALOON = "alignBaloonWidth";
 
     private OptionInfo optInfo;
-
-    private boolean useConfigPath;
-    private File configPath;
-
+    private ConfigStore configStore;
     private FontInfo fontInfo = FontInfo.DEFAULT_FONTINFO;
 
     private int frameWidth  = 800;
@@ -58,37 +55,45 @@ public class AppSetting{
     }
 
     /**
-     * コマンドラインオプションからアプリ設定を展開する。
-     * @param optionInfo オプション情報
+     * 設定格納ディレクトリ関係の解析。
+     * @param optionInfo コマンドライン情報
+     * @return 設定ディレクトリ情報
      */
-    public void applyOptionInfo(OptionInfo optionInfo){
-        this.optInfo = optionInfo;
-        applyConfigPathSetting();
-        applyFontSetting();
-        applyGeometrySetting();
-        return;
-    }
+    private static ConfigStore parseConfigStore(OptionInfo optionInfo){
+        CmdOption opt =
+                optionInfo.getExclusiveOption(CmdOption.OPT_CONFDIR,
+                                              CmdOption.OPT_NOCONF );
+
+        boolean useConfig;
+        File configPath;
 
-    /**
-     * 設定格納ディレクトリ関係の設定。
-     */
-    private void applyConfigPathSetting(){
-        CmdOption opt = this.optInfo
-                .getExclusiveOption(CmdOption.OPT_CONFDIR,
-                                    CmdOption.OPT_NOCONF );
         if(opt == CmdOption.OPT_NOCONF){
-            this.useConfigPath = false;
-            this.configPath = null;
+            useConfig = false;
+            configPath = null;
         }else if(opt == CmdOption.OPT_CONFDIR){
-            this.useConfigPath = true;
-            String path = this.optInfo.getStringArg(CmdOption.OPT_CONFDIR);
-            this.configPath = FileUtils.supplyFullPath(new File(path));
+            String path = optionInfo.getStringArg(CmdOption.OPT_CONFDIR);
+            useConfig = true;
+            configPath = FileUtils.supplyFullPath(new File(path));
         }else{
-            this.useConfigPath = true;
+            useConfig = true;
             File path = ConfigFile.getImplicitConfigDirectory();
-            this.configPath = path;
+            configPath = path;
         }
 
+        ConfigStore result = new ConfigStore(useConfig, configPath);
+
+        return result;
+    }
+
+    /**
+     * コマンドラインオプションからアプリ設定を展開する。
+     * @param optionInfo オプション情報
+     */
+    public void applyOptionInfo(OptionInfo optionInfo){
+        this.optInfo = optionInfo;
+        this.configStore = parseConfigStore(optionInfo);
+        applyFontSetting();
+        applyGeometrySetting();
         return;
     }
 
@@ -97,33 +102,27 @@ public class AppSetting{
      */
     private void applyFontSetting(){
         String fontName = this.optInfo.getStringArg(CmdOption.OPT_INITFONT);
+
         Boolean useAntiAlias =
                 this.optInfo.getBooleanArg(CmdOption.OPT_ANTIALIAS);
+        if(useAntiAlias == null){
+            useAntiAlias = this.fontInfo.isAntiAliased();
+        }
+
         Boolean useFractional =
                 this.optInfo.getBooleanArg(CmdOption.OPT_FRACTIONAL);
+        if(useFractional == null){
+            useFractional = this.fontInfo.usesFractionalMetrics();
+        }
 
         if(fontName != null){
             Font font = Font.decode(fontName);
             this.fontInfo = this.fontInfo.deriveFont(font);
         }
 
-        if(useAntiAlias != null){
-            FontRenderContext context = this.fontInfo.getFontRenderContext();
-            FontRenderContext newContext =
-                    new FontRenderContext(context.getTransform(),
-                                          useAntiAlias.booleanValue(),
-                                          context.usesFractionalMetrics() );
-            this.fontInfo = this.fontInfo.deriveRenderContext(newContext);
-        }
-
-        if(useFractional != null){
-            FontRenderContext context = this.fontInfo.getFontRenderContext();
-            FontRenderContext newContext =
-                    new FontRenderContext(context.getTransform(),
-                                          context.isAntiAliased(),
-                                          useFractional.booleanValue() );
-            this.fontInfo = this.fontInfo.deriveRenderContext(newContext);
-        }
+        this.fontInfo =
+                this.fontInfo.deriveRenderContext(useAntiAlias,
+                                                  useFractional );
 
         return;
     }
@@ -150,37 +149,19 @@ public class AppSetting{
     }
 
     /**
-     * 設定格納ディレクトリを返す。
-     * @return 設定格納ディレクトリ。
-     */
-    public File getConfigPath(){
-        return this.configPath;
-    }
-
-    /**
-     * 設定格納ディレクトリを設定する。
-     * @param path 設定格納ディレクトリ
+     * コマンドラインオプション情報を返す。
+     * @return コマンドラインオプション情報
      */
-    public void setConfigPath(File path){
-        this.configPath = path;
-        return;
+    public OptionInfo getOptionInfo(){
+        return this.optInfo;
     }
 
     /**
-     * 設定格納ディレクトリを使うか否かを返す。
-     * @return 使うならtrue
+     * 設定格納情報を返す。
+     * @return 設定格納情報
      */
-    public boolean useConfigPath(){
-        return this.useConfigPath;
-    }
-
-    /**
-     * 設定格納ディレクトリを使うか否か設定する。
-     * @param need 使うならtrue
-     */
-    public void setUseConfigPath(boolean need){
-        this.useConfigPath = need;
-        return;
+    public ConfigStore getConfigStore(){
+        return this.configStore;
     }
 
     /**
@@ -274,16 +255,11 @@ public class AppSetting{
      * ネットワーク設定をロードする。
      */
     private void loadNetConfig(){
-        if( ! useConfigPath() ) return;
-
-        JsValue value = ConfigFile.loadJson(new File(NETCONFIG_FILE));
-        if(value == null) return;
-        this.loadedNetConfig = value;
+        JsObject root = this.configStore.loadNetConfig();
+        if(root == null) return;
+        this.loadedNetConfig = root;
 
-        if( ! (value instanceof JsObject) ) return;
-        JsObject root = (JsObject) value;
-
-        value = root.getValue(HASH_PROXY);
+        JsValue value = root.getValue(HASH_PROXY);
         if( ! (value instanceof JsObject) ) return;
         JsObject proxy = (JsObject) value;
 
@@ -298,16 +274,11 @@ public class AppSetting{
      * 会話表示設定をロードする。
      */
     private void loadTalkConfig(){
-        if( ! useConfigPath() ) return;
-
-        JsValue value = ConfigFile.loadJson(new File(TALKCONFIG_FILE));
-        if(value == null) return;
-        this.loadedTalkConfig = value;
-
-        if( ! (value instanceof JsObject) ) return;
-        JsObject root = (JsObject) value;
+        JsObject root = this.configStore.loadTalkConfig();
+        if(root == null) return;
+        this.loadedTalkConfig = root;
 
-        value = root.getValue(HASH_FONT);
+        JsValue value = root.getValue(HASH_FONT);
         if(value instanceof JsObject){
             JsObject font = (JsObject) value;
             FontInfo info = FontInfo.decodeJson(font);
@@ -346,7 +317,7 @@ public class AppSetting{
      * ネットワーク設定をセーブする。
      */
     private void saveNetConfig(){
-        if( ! useConfigPath() ) return;
+        if( ! getConfigStore().useStoreFile() ) return;
 
         JsObject root = new JsObject();
         JsObject proxy = ProxyInfo.buildJson(getProxyInfo());
@@ -356,7 +327,7 @@ public class AppSetting{
             if(this.loadedNetConfig.equals(root)) return;
         }
 
-        ConfigFile.saveJson(new File(NETCONFIG_FILE), root);
+        this.configStore.saveNetConfig(root);
 
         return;
     }
@@ -365,7 +336,7 @@ public class AppSetting{
      * 会話表示設定をセーブする。
      */
     private void saveTalkConfig(){
-        if( ! useConfigPath() ) return;
+        if( ! getConfigStore().useStoreFile() ) return;
 
         JsObject root = new JsObject();
 
@@ -390,7 +361,7 @@ public class AppSetting{
             if(this.loadedTalkConfig.equals(root)) return;
         }
 
-        ConfigFile.saveJson(new File(TALKCONFIG_FILE), root);
+        this.configStore.saveTalkConfig(root);
 
         return;
     }
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.config;
 
-import java.io.IOException;
-import java.util.LinkedList;
+import java.util.Arrays;
 import java.util.List;
+import jp.sfjp.jindolf.ResourceManager;
 
 /**
  * コマンドラインオプションの列挙。
  */
-public enum CmdOption{
+public enum CmdOption {
 
     /** ヘルプ。 */
-    OPT_HELP("help", "h", "-help", "?"),
+    OPT_HELP("-help", "-h", "--help", "-?"),
     /** 版数表示。 */
-    OPT_VERSION("version"),
+    OPT_VERSION("-version"),
     /** UI文字制御。 */
-    OPT_BOLDMETAL("boldMetal"),
+    OPT_BOLDMETAL("-boldMetal"),
     /** スプラッシュ制御。 */
-    OPT_NOSPLASH("nosplash"),
+    OPT_NOSPLASH("-nosplash"),
     /** ウィンドウ位置指定。 */
-    OPT_GEOMETRY("geometry"),
+    OPT_GEOMETRY("-geometry"),
     /** 実行環境出力。 */
-    OPT_VMINFO("vminfo"),
+    OPT_VMINFO("-vminfo"),
     /** コンソールログ。 */
-    OPT_CONSOLELOG("consolelog"),
+    OPT_CONSOLELOG("-consolelog"),
     /** フォント指定。 */
-    OPT_INITFONT("initfont"),
+    OPT_INITFONT("-initfont"),
     /** アンチエイリアス。 */
-    OPT_ANTIALIAS("antialias"),
+    OPT_ANTIALIAS("-antialias"),
     /** サブピクセル制御。 */
-    OPT_FRACTIONAL("fractional"),
+    OPT_FRACTIONAL("-fractional"),
     /** 設定格納ディレクトリ指定。 */
-    OPT_CONFDIR("confdir"),
+    OPT_CONFDIR("-confdir"),
     /** 設定格納ディレクトリ不使用。 */
-    OPT_NOCONF("noconfdir"),
+    OPT_NOCONF("-noconfdir"),
     ;
 
 
-    private final List<String> nameList = new LinkedList<String>();
+    private static final String RES_HELPTEXT = "resources/help.txt";
+
+
+    private final List<String> nameList;
 
 
     /**
      * コンストラクタ。
      * @param names 頭のハイフンを除いたオプション名の一覧
      */
-    private CmdOption(CharSequence ... names){
-        if(names == null) throw new NullPointerException();
-        if(names.length <= 0) throw new IllegalArgumentException();
-
-        for(CharSequence name : names){
-            if(name == null) throw new NullPointerException();
-            this.nameList.add(name.toString().intern());
-        }
-
+    private CmdOption(String ... names){
+        assert names.length > 0;
+        this.nameList = Arrays.asList(names);
         return;
     }
 
 
     /**
+     * ヘルプメッセージ(オプションの説明)を返す。
+     * @return ヘルプメッセージ
+     */
+    public static CharSequence getHelpText(){
+        CharSequence helpText =
+            ResourceManager.getTextFile(RES_HELPTEXT);
+
+        return helpText;
+    }
+
+    /**
      * オプション名に合致するEnumを返す。
-     * @param seq ハイフン付きオプション名
+     * @param arg 個別のコマンドライン引数
      * @return 合致したEnum。どれとも合致しなければnull
      */
-    public static CmdOption parseCmdOption(CharSequence seq){
+    public static CmdOption parseCmdOption(String arg){
         for(CmdOption option : values()){
-            if(option.matchHyphened(seq)) return option;
+            if(option.matches(arg)) return option;
         }
         return null;
     }
 
     /**
+     * 任意のオプション文字列がこのオプションに合致するか判定する。
+     * @param option ハイフンの付いたオプション文字列
+     * @return 合致すればtrue
+     */
+    public boolean matches(String option){
+        for(String name : this.nameList){
+            if(option.equals(name)) return true;
+        }
+
+        return false;
+    }
+
+    /**
      * 単体で意味をなすオプションか判定する。
-     * @param option オプション
      * @return 単体で意味をなすならtrue
      */
-    public static boolean isIndepOption(CmdOption option){
-        switch(option){
+    public boolean isIndepOption(){
+        switch(this){
         case OPT_HELP:
         case OPT_VERSION:
         case OPT_VMINFO:
@@ -99,11 +119,10 @@ public enum CmdOption{
 
     /**
      * 真偽指定を一つ必要とするオプションか判定する。
-     * @param option オプション
      * @return 真偽指定を一つ必要とするオプションならtrue
      */
-    public static boolean isBooleanOption(CmdOption option){
-        switch(option){
+    public boolean isBooleanOption(){
+        switch(this){
         case OPT_ANTIALIAS:
         case OPT_FRACTIONAL:
             return true;
@@ -115,22 +134,6 @@ public enum CmdOption{
     }
 
     /**
-     * ヘルプメッセージ(オプションの説明)を返す。
-     * @return ヘルプメッセージ
-     */
-    public static CharSequence getHelpText(){
-        CharSequence helpText;
-
-        try{
-            helpText = Jindolf.loadResourceText("resources/help.txt");
-        }catch(IOException e){
-            helpText = "";
-        }
-
-        return helpText;
-    }
-
-    /**
      * 頭のハイフンを除いたオプション名を返す。
      * オプション名が複数指定されていた場合は最初のオプション名
      * @return オプション名
@@ -140,29 +143,4 @@ public enum CmdOption{
         return this.nameList.get(0);
     }
 
-    /**
-     * 頭のハイフンが付いたオプション名を返す。
-     * オプション名が複数指定されていた場合は最初のオプション名
-     * @return オプション名
-     */
-    public String toHyphened(){
-        return "-" + toString();
-    }
-
-    /**
-     * 任意のオプション文字列がこのオプションに合致するか判定する。
-     * @param option ハイフンの付いたオプション文字列
-     * @return 合致すればtrue
-     */
-    public boolean matchHyphened(CharSequence option){
-        if(option == null) return false;
-
-        for(String name : this.nameList){
-            String hyphened = "-" + name;
-            if(hyphened.equals(option.toString())) return true;
-        }
-
-        return false;
-    }
-
 }
@@ -5,36 +5,21 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
-
-import java.awt.Component;
-import java.awt.HeadlessException;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
+package jp.sfjp.jindolf.config;
+
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
-import java.io.Reader;
 import java.io.Writer;
 import java.nio.charset.Charset;
-import javax.swing.ButtonGroup;
-import javax.swing.JButton;
 import javax.swing.JDialog;
 import javax.swing.JOptionPane;
-import javax.swing.JRadioButton;
-import jp.sourceforge.jovsonz.JsComposition;
-import jp.sourceforge.jovsonz.JsParseException;
-import jp.sourceforge.jovsonz.JsVisitException;
-import jp.sourceforge.jovsonz.Json;
+import jp.sfjp.jindolf.VerInfo;
+import jp.sfjp.jindolf.log.LogWrapper;
+import jp.sfjp.jindolf.view.LockErrorPane;
 
 /**
  * Jindolf設定格納ディレクトリに関するあれこれ。
@@ -42,7 +27,7 @@ import jp.sourceforge.jovsonz.Json;
 public final class ConfigFile{
 
     private static final String TITLE_BUILDCONF =
-            Jindolf.TITLE + "設定格納ディレクトリの設定";
+            VerInfo.TITLE + "設定格納ディレクトリの設定";
 
     private static final String JINCONF     = "Jindolf";
     private static final String JINCONF_DOT = ".jindolf";
@@ -52,14 +37,16 @@ public final class ConfigFile{
 
     private static final String MSG_POST =
             "<ul>"
-            + "<li><code>" + CmdOption.OPT_CONFDIR.toHyphened() + "</code>"
+            + "<li><code>" + CmdOption.OPT_CONFDIR + "</code>"
             + "&nbsp;オプション指定により、<br>"
             + "任意の設定格納ディレクトリを指定することができます。<br>"
-            + "<li><code>" + CmdOption.OPT_NOCONF.toHyphened() + "</code>"
+            + "<li><code>" + CmdOption.OPT_NOCONF + "</code>"
             + "&nbsp;オプション指定により、<br>"
             + "設定格納ディレクトリを使わずに起動することができます。<br>"
             + "</ul>";
 
+    private static final LogWrapper LOGGER = new LogWrapper();
+
 
     /**
      * 隠れコンストラクタ。
@@ -72,19 +59,19 @@ public final class ConfigFile{
 
     /**
      * 設定格納ディレクトリのセットアップ。
+     * @param configStore 設定ディレクトリ
      * @return 設定格納ディレクトリ
      */
-    public static File setupConfigDirectory(){
-        AppSetting setting = Jindolf.getAppSetting();
+    public static File setupConfigDirectory(ConfigStore configStore){
         File configPath;
 
-        if( ! setting.useConfigPath() ){
+        if( ! configStore.useStoreFile() ){
             configPath = null;
         }else{
             String optName;
-            if(setting.getConfigPath() != null){
-                configPath = setting.getConfigPath();
-                optName = CmdOption.OPT_CONFDIR.toHyphened();
+            if(configStore.getConfigPath() != null){
+                configPath = configStore.getConfigPath();
+                optName = CmdOption.OPT_CONFDIR.toString();
             }else{
                 configPath = ConfigFile.getImplicitConfigDirectory();
                 optName = null;
@@ -96,19 +83,18 @@ public final class ConfigFile{
             ConfigFile.checkAccessibility(configPath);
         }
 
-        setting.setConfigPath(configPath);
+        configStore.setConfigPath(configPath);
 
         return configPath;
     }
 
     /**
      * ロックファイルのセットアップ。
+     * @param configStore 設定ディレクトリ
      * @return ロックオブジェクト
      */
-    public static InterVMLock setupLockFile(){
-        AppSetting setting = Jindolf.getAppSetting();
-
-        File configPath = setting.getConfigPath();
+    public static InterVMLock setupLockFile(ConfigStore configStore){
+        File configPath = configStore.getConfigPath();
         if(configPath == null) return null;
 
         File lockFile = new File(configPath, "lock");
@@ -119,8 +105,8 @@ public final class ConfigFile{
         if( ! lock.isFileOwner() ){
             confirmLockError(lock);
             if( ! lock.isFileOwner() ){
-                setting.setConfigPath(null);
-                setting.setUseConfigPath(false);
+                configStore.setConfigPath(null);
+                configStore.setUseStoreFile(false);
             }
         }
 
@@ -143,7 +129,7 @@ public final class ConfigFile{
     public static File getImplicitConfigDirectory(){
         File result;
 
-        File jarParent = FileUtils.getJarDirectory(Jindolf.class);
+        File jarParent = FileUtils.getJarDirectory();
         if(jarParent != null && FileUtils.isAccessibleDirectory(jarParent)){
             result = new File(jarParent, JINCONF);
             if(FileUtils.isAccessibleDirectory(result)){
@@ -284,6 +270,15 @@ public final class ConfigFile{
     }
 
     /**
+     * VMを異常終了させる。
+     */
+    private static void abort(){
+        System.exit(1);
+        assert false;
+        return;
+    }
+
+    /**
      * 設定ディレクトリのルートファイルシステムもしくはドライブレターに
      * アクセスできないエラーをダイアログに提示し、VM終了する。
      * @param path 設定ディレクトリ
@@ -299,7 +294,7 @@ public final class ConfigFile{
                 + "起動を中止します。<br>"
                 + MSG_POST
                 + "</html>" );
-        Jindolf.exit(1);
+        abort();
         return;
     }
 
@@ -320,7 +315,7 @@ public final class ConfigFile{
                 + "起動を中止します。<br>"
                 + MSG_POST
                 + "</html>" );
-        Jindolf.exit(1);
+        abort();
         return;
     }
 
@@ -369,7 +364,7 @@ public final class ConfigFile{
                 + "設定ディレクトリの作成をせずに起動を中止します。<br>"
                 + MSG_POST
                 + "</html>" );
-        Jindolf.exit(1);
+        abort();
         return;
     }
 
@@ -387,7 +382,7 @@ public final class ConfigFile{
                 + "起動を中止します。<br>"
                 + MSG_POST
                 + "</html>" );
-        Jindolf.exit(1);
+        abort();
         return;
     }
 
@@ -407,7 +402,7 @@ public final class ConfigFile{
                 + "読み書きできるようにしてください。<br>"
                 + MSG_POST
                 + "</html>" );
-        Jindolf.exit(1);
+        abort();
         return;
     }
 
@@ -423,7 +418,7 @@ public final class ConfigFile{
                 + "への書き込みができません。"
                 + "起動を中止します。<br>"
                 + "</html>" );
-        Jindolf.exit(1);
+        abort();
         return;
     }
 
@@ -520,7 +515,7 @@ public final class ConfigFile{
             dialog.dispose();
 
             if(pane.isAborted() || pane.getValue() == null){
-                Jindolf.exit(1);
+                abort();
                 break;
             }else if(pane.isRadioRetry()){
                 lock.tryLock();
@@ -531,7 +526,7 @@ public final class ConfigFile{
                         + "設定ディレクトリを使わずに起動を続行します。<br>"
                         + "今回、各種設定の読み込み・保存はできません。<br>"
                         + "<code>"
-                        + CmdOption.OPT_NOCONF.toHyphened()
+                        + CmdOption.OPT_NOCONF
                         + "</code> オプション"
                         + "を使うとこの警告は出なくなります。"
                         + "</html>");
@@ -549,7 +544,7 @@ public final class ConfigFile{
                             + "を削除してください。<br>"
                             + "起動を中止します。"
                             + "</html>");
-                    Jindolf.exit(1);
+                    abort();
                     break;
                 }
                 lock.tryLock();
@@ -561,7 +556,7 @@ public final class ConfigFile{
                         + "を確保することができません。<br>"
                         + "起動を中止します。"
                         + "</html>");
-                Jindolf.exit(1);
+                abort();
                 break;
             }
         }
@@ -569,289 +564,4 @@ public final class ConfigFile{
         return;
     }
 
-    /**
-     * 設定ディレクトリ上のJSONファイルを読み込む。
-     * @param file JSONファイルの相対パス
-     * @return JSON objectまたはarray。
-     * 設定ディレクトリを使わない設定、
-     * もしくはJSONファイルが存在しない、
-     * もしくは入力エラーがあればnull
-     */
-    public static JsComposition loadJson(File file){
-        AppSetting setting = Jindolf.getAppSetting();
-        if( ! setting.useConfigPath() ) return null;
-
-        File absFile;
-        if(file.isAbsolute()){
-            absFile = file;
-        }else{
-            File configPath = setting.getConfigPath();
-            if(configPath == null) return null;
-            absFile = new File(configPath, file.getPath());
-            if( ! absFile.exists() ) return null;
-            if( ! absFile.isAbsolute() ) return null;
-        }
-
-        InputStream istream;
-        try{
-            istream = new FileInputStream(absFile);
-        }catch(FileNotFoundException e){
-            assert false;
-            return null;
-        }
-        istream = new BufferedInputStream(istream);
-
-        Reader reader = new InputStreamReader(istream, CHARSET_JSON);
-
-        JsComposition<?> root;
-        try{
-            root = Json.parseJson(reader);
-        }catch(IOException e){
-            Jindolf.logger().fatal(
-                    "JSONファイル["
-                    + absFile.getPath()
-                    + "]の読み込み時に支障がありました。", e);
-            return null;
-        }catch(JsParseException e){
-            Jindolf.logger().fatal(
-                    "JSONファイル["
-                    + absFile.getPath()
-                    + "]の内容に不備があります。", e);
-            return null;
-        }finally{
-            try{
-                reader.close();
-            }catch(IOException e){
-                Jindolf.logger().fatal(
-                        "JSONファイル["
-                        + absFile.getPath()
-                        + "]を閉じることができません。", e);
-                return null;
-            }
-        }
-
-        return root;
-    }
-
-    /**
-     * 設定ディレクトリ上のJSONファイルに書き込む。
-     * @param file JSONファイルの相対パス
-     * @param root JSON objectまたはarray
-     * @return 正しくセーブが行われればtrue。
-     * 何らかの理由でセーブが完了できなければfalse
-     */
-    public static boolean saveJson(File file, JsComposition root){
-        AppSetting setting = Jindolf.getAppSetting();
-        if( ! setting.useConfigPath() ) return false;
-        File configPath = setting.getConfigPath();
-        if(configPath == null) return false;
-
-        // TODO テンポラリファイルを用いたより安全なファイル更新
-        File absFile = new File(configPath, file.getPath());
-        absFile.delete();
-        try{
-            if(absFile.createNewFile() != true) return false;
-        }catch(IOException e){
-            Jindolf.logger().fatal(
-                    "JSONファイル["
-                    + absFile.getPath()
-                    + "]の新規生成ができません。", e);
-            return false;
-        }
-
-        OutputStream ostream;
-        try{
-            ostream = new FileOutputStream(absFile);
-        }catch(FileNotFoundException e){
-            assert false;
-            return false;
-        }
-        ostream = new BufferedOutputStream(ostream);
-        Writer writer = new OutputStreamWriter(ostream, CHARSET_JSON);
-
-        try{
-            Json.dumpJson(writer, root);
-        }catch(JsVisitException e){
-            Jindolf.logger().fatal(
-                    "JSONファイル["
-                    + absFile.getPath()
-                    + "]の出力処理で支障がありました。", e);
-            return false;
-        }catch(IOException e){
-            Jindolf.logger().fatal(
-                    "JSONファイル["
-                    + absFile.getPath()
-                    + "]の書き込み時に支障がありました。", e);
-            return false;
-        }finally{
-            try{
-                writer.close();
-            }catch(IOException e){
-                Jindolf.logger().fatal(
-                        "JSONファイル["
-                        + absFile.getPath()
-                        + "]を閉じることができません。", e);
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    /**
-     * ロックエラー用ダイアログ。
-     * <ul>
-     * <li>強制解除
-     * <li>リトライ
-     * <li>設定ディレクトリを無視
-     * <li>起動中止
-     * </ul>
-     * の選択を利用者に求める。
-     */
-    @SuppressWarnings("serial")
-    private static class LockErrorPane
-            extends JOptionPane
-            implements ActionListener{
-
-        private final InterVMLock lock;
-
-        private final JRadioButton continueButton =
-                new JRadioButton("設定ディレクトリを使わずに起動を続行");
-        private final JRadioButton retryButton =
-                new JRadioButton("再度ロック取得を試す");
-        private final JRadioButton forceButton =
-                new JRadioButton(
-                "<html>"
-                + "ロックを強制解除<br>"
-                + " (※他のJindolfと設定ファイル書き込みが衝突するかも…)"
-                + "</html>");
-
-        private final JButton okButton = new JButton("OK");
-        private final JButton abortButton = new JButton("起動中止");
-
-        private boolean aborted = false;
-
-        /**
-         * コンストラクタ。
-         * @param lock 失敗したロック
-         */
-        public LockErrorPane(InterVMLock lock){
-            super();
-
-            this.lock = lock;
-
-            String htmlMessage =
-                    "<html>"
-                    + "設定ディレクトリのロックファイル<br>"
-                    + getCenteredFileName(this.lock.getLockFile())
-                    + "のロックに失敗しました。<br>"
-                    + "考えられる原因としては、<br>"
-                    + "<ul>"
-                    + "<li>前回起動したJindolfの終了が正しく行われなかった"
-                    + "<li>今どこかで他のJindolfが動いている"
-                    + "</ul>"
-                    + "などが考えられます。<br>"
-                    + "<hr>"
-                    + "</html>";
-
-            ButtonGroup bgrp = new ButtonGroup();
-            bgrp.add(this.continueButton);
-            bgrp.add(this.retryButton);
-            bgrp.add(this.forceButton);
-            this.continueButton.setSelected(true);
-
-            Object[] msg = {
-                htmlMessage,
-                this.continueButton,
-                this.retryButton,
-                this.forceButton,
-            };
-            setMessage(msg);
-
-            Object[] opts = {
-                this.okButton,
-                this.abortButton,
-            };
-            setOptions(opts);
-
-            setMessageType(JOptionPane.ERROR_MESSAGE);
-
-            this.okButton   .addActionListener(this);
-            this.abortButton.addActionListener(this);
-
-            return;
-        }
-
-        /**
-         * 「設定ディレクトリを無視して続行」が選択されたか判定する。
-         * @return 「無視して続行」が選択されていればtrue
-         */
-        public boolean isRadioContinue(){
-            return this.continueButton.isSelected();
-        }
-
-        /**
-         * 「リトライ」が選択されたか判定する。
-         * @return 「リトライ」が選択されていればtrue
-         */
-        public boolean isRadioRetry(){
-            return this.retryButton.isSelected();
-        }
-
-        /**
-         * 「強制解除」が選択されたか判定する。
-         * @return 「強制解除」が選択されていればtrue
-         */
-        public boolean isRadioForce(){
-            return this.forceButton.isSelected();
-        }
-
-        /**
-         * 「起動中止」が選択されたか判定する。
-         * @return 「起動中止」が押されていたならtrue
-         */
-        public boolean isAborted(){
-            return this.aborted;
-        }
-
-        /**
-         * {@inheritDoc}
-         * @param parentComponent {@inheritDoc}
-         * @param title {@inheritDoc}
-         * @return {@inheritDoc}
-         * @throws HeadlessException {@inheritDoc}
-         */
-        @Override
-        public JDialog createDialog(Component parentComponent,
-                                      String title)
-                throws HeadlessException{
-            final JDialog dialog =
-                    super.createDialog(parentComponent, title);
-
-            ActionListener listener = new ActionListener(){
-                public void actionPerformed(ActionEvent event){
-                    dialog.setVisible(false);
-                    return;
-                }
-            };
-
-            this.okButton   .addActionListener(listener);
-            this.abortButton.addActionListener(listener);
-
-            return dialog;
-        }
-
-        /**
-         * ボタン押下を受信する。
-         * @param event イベント
-         */
-        public void actionPerformed(ActionEvent event){
-            Object source = event.getSource();
-            if(source == this.okButton) this.aborted = false;
-            else                        this.aborted = true;
-            return;
-        }
-
-    }
-
 }
diff --git a/src/main/java/jp/sfjp/jindolf/config/ConfigStore.java b/src/main/java/jp/sfjp/jindolf/config/ConfigStore.java
new file mode 100644 (file)
index 0000000..beecf80
--- /dev/null
@@ -0,0 +1,391 @@
+/*
+ * config store
+ *
+ * License : The MIT License
+ * Copyright(c) 2012 olyutorskii
+ */
+
+package jp.sfjp.jindolf.config;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import jp.sfjp.jindolf.log.LogWrapper;
+import jp.sourceforge.jovsonz.JsComposition;
+import jp.sourceforge.jovsonz.JsObject;
+import jp.sourceforge.jovsonz.JsParseException;
+import jp.sourceforge.jovsonz.JsTypes;
+import jp.sourceforge.jovsonz.JsVisitException;
+import jp.sourceforge.jovsonz.Json;
+
+/**
+ * 各種設定の永続化関連。
+ */
+public class ConfigStore {
+
+    /** 検索履歴ファイル。 */
+    public static final File HIST_FILE = new File("searchHistory.json");
+    /** 原稿ファイル。 */
+    public static final File DRAFT_FILE = new File("draft.json");
+    /** ネットワーク設定ファイル。 */
+    public static final File NETCONFIG_FILE = new File("netconfig.json");
+    /** 台詞表示設定ファイル。 */
+    public static final File TALKCONFIG_FILE = new File("talkconfig.json");
+
+    private static final Charset CHARSET_JSON = Charset.forName("UTF-8");
+
+    private static final LogWrapper LOGGER = new LogWrapper();
+
+
+    private boolean useStoreFile;
+    private File configPath;
+
+
+    /**
+     * コンストラクタ。
+     * @param useStoreFile 設定ディレクトリへの永続化機能を使うならtrue
+     * @param configPath 設定ディレクトリ。
+     * 設定ディレクトリを使わない場合は無視され、nullとして扱われる。
+     */
+    public ConfigStore(boolean useStoreFile, File configPath){
+        super();
+
+        this.useStoreFile = useStoreFile;
+
+        File path = null;
+        if(this.useStoreFile) path = configPath;
+        this.configPath = path;
+
+        return;
+    }
+
+    /**
+     * 設定ディレクトリを使うか否か判定する。
+     * @return 設定ディレクトリを使うならtrue。
+     */
+    public boolean useStoreFile(){
+        return this.useStoreFile;
+    }
+
+    /**
+     * 設定ディレクトリ利用の有無を変更する。
+     * @param sw 利用するならtrue
+     */
+    public void setUseStoreFile(boolean sw){
+        this.useStoreFile = sw;
+        if( ! this.useStoreFile ){
+            this.configPath = null;
+        }
+        return;
+    }
+
+    /**
+     * 設定ディレクトリを返す。
+     * @return 設定ディレクトリ。設定ディレクトリを使わない場合はnull
+     */
+    public File getConfigPath(){
+        File result;
+        if(this.useStoreFile) result = this.configPath;
+        else                  result = null;
+        return result;
+    }
+
+    /**
+     * 設定ディレクトリを変更する。
+     * @param path 設定ディレクトリ
+     */
+    public void setConfigPath(File path){
+        this.configPath = path;
+        return;
+    }
+
+    /**
+     * 設定ディレクトリ上のOBJECT型JSONファイルを読み込む。
+     * @param file JSONファイルの相対パス。
+     * @return JSON object。
+     * 設定ディレクトリを使わない設定、
+     * もしくはJSONファイルが存在しない、
+     * もしくはOBJECT型でなかった、
+     * もしくは入力エラーがあればnull
+     */
+    public JsObject loadJsObject(File file){
+        JsComposition<?> root = loadJson(file);
+        if(root == null || root.getJsTypes() != JsTypes.OBJECT) return null;
+        JsObject result = (JsObject) root;
+        return result;
+    }
+
+    /**
+     * 設定ディレクトリ上のJSONファイルを読み込む。
+     * @param file JSONファイルの相対パス
+     * @return JSON objectまたはarray。
+     * 設定ディレクトリを使わない設定、
+     * もしくはJSONファイルが存在しない、
+     * もしくは入力エラーがあればnull
+     */
+    public JsComposition<?> loadJson(File file){
+        if( ! this.useStoreFile ) return null;
+
+        File absFile;
+        if(file.isAbsolute()){
+            absFile = file;
+        }else{
+            if(this.configPath == null) return null;
+            absFile = new File(this.configPath, file.getPath());
+            if( ! absFile.exists() ) return null;
+            if( ! absFile.isAbsolute() ) return null;
+        }
+        String absPath = absFile.getPath();
+
+        InputStream istream;
+        try{
+            istream = new FileInputStream(absFile);
+        }catch(FileNotFoundException e){
+            assert false;
+            return null;
+        }
+        istream = new BufferedInputStream(istream);
+
+        JsComposition<?> root;
+        try{
+            root = loadJson(istream);
+        }catch(IOException e){
+            LOGGER.fatal(
+                    "JSONファイル["
+                    + absPath
+                    + "]の読み込み時に支障がありました。", e);
+            return null;
+        }catch(JsParseException e){
+            LOGGER.fatal(
+                    "JSONファイル["
+                    + absPath
+                    + "]の内容に不備があります。", e);
+            return null;
+        }finally{
+            try{
+                istream.close();
+            }catch(IOException e){
+                LOGGER.fatal(
+                        "JSONファイル["
+                        + absPath
+                        + "]を閉じることができません。", e);
+                return null;
+            }
+        }
+
+        return root;
+    }
+
+    /**
+     * バイトストリーム上のJSONデータを読み込む。
+     * <p>バイトストリームはUTF-8と解釈される。
+     * @param is バイトストリーム
+     * @return JSON objectまたはarray。
+     * @throws IOException 入力エラー
+     * @throws JsParseException 構文エラー
+     */
+    protected JsComposition<?> loadJson(InputStream is)
+            throws IOException, JsParseException {
+        Reader reader = new InputStreamReader(is, CHARSET_JSON);
+        reader = new BufferedReader(reader);
+        JsComposition<?> root = loadJson(reader);
+        return root;
+    }
+
+    /**
+     * 文字ストリーム上のJSONデータを読み込む。
+     * @param reader 文字ストリーム
+     * @return JSON objectまたはarray。
+     * @throws IOException 入力エラー
+     * @throws JsParseException 構文エラー
+     */
+    protected JsComposition<?> loadJson(Reader reader)
+            throws IOException, JsParseException {
+        JsComposition<?> root = Json.parseJson(reader);
+        return root;
+    }
+
+    /**
+     * 設定ディレクトリ上のJSONファイルに書き込む。
+     * @param file JSONファイルの相対パス
+     * @param root JSON objectまたはarray
+     * @return 正しくセーブが行われればtrue。
+     * 何らかの理由でセーブが完了できなければfalse
+     */
+    public boolean saveJson(File file, JsComposition<?> root){
+        if( ! this.useStoreFile ) return false;
+
+        // TODO テンポラリファイルを用いたより安全なファイル更新
+        File absFile = new File(this.configPath, file.getPath());
+        String absPath = absFile.getPath();
+
+        absFile.delete();
+        try{
+            if(absFile.createNewFile() != true) return false;
+        }catch(IOException e){
+            LOGGER.fatal(
+                    "JSONファイル["
+                    + absPath
+                    + "]の新規生成ができません。", e);
+            return false;
+        }
+
+        OutputStream ostream;
+        try{
+            ostream = new FileOutputStream(absFile);
+        }catch(FileNotFoundException e){
+            assert false;
+            return false;
+        }
+        ostream = new BufferedOutputStream(ostream);
+
+        try{
+            saveJson(ostream, root);
+        }catch(JsVisitException e){
+            LOGGER.fatal(
+                    "JSONファイル["
+                    + absPath
+                    + "]の出力処理で支障がありました。", e);
+            return false;
+        }catch(IOException e){
+            LOGGER.fatal(
+                    "JSONファイル["
+                    + absPath
+                    + "]の書き込み時に支障がありました。", e);
+            return false;
+        }finally{
+            try{
+                ostream.close();
+            }catch(IOException e){
+                LOGGER.fatal(
+                        "JSONファイル["
+                        + absPath
+                        + "]を閉じることができません。", e);
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * バイトストリームにJSONデータを書き込む。
+     * <p>バイトストリームはUTF-8と解釈される。
+     * @param os バイトストリーム出力
+     * @param root JSON objectまたはarray
+     * @throws IOException 出力エラー
+     * @throws JsVisitException 構造エラー
+     */
+    protected void saveJson(OutputStream os, JsComposition<?> root)
+            throws IOException, JsVisitException {
+        Writer writer = new OutputStreamWriter(os, CHARSET_JSON);
+        writer = new BufferedWriter(writer);
+        saveJson(writer, root);
+        return;
+    }
+
+    /**
+     * 文字ストリームにJSONデータを書き込む。
+     * @param writer 文字ストリーム出力
+     * @param root JSON objectまたはarray
+     * @throws IOException 出力エラー
+     * @throws JsVisitException 構造エラー
+     */
+    protected void saveJson(Writer writer, JsComposition<?> root)
+            throws IOException, JsVisitException {
+        Json.dumpJson(writer, root);
+        return;
+    }
+
+    /**
+     * 検索履歴ファイルを読み込む。
+     * @return 履歴データ。履歴を読まないもしくは読めない場合はnull
+     */
+    public JsObject loadHistoryConfig(){
+        JsObject result = loadJsObject(HIST_FILE);
+        return result;
+    }
+
+    /**
+     * 原稿ファイルを読み込む。
+     * @return 原稿データ。原稿を読まないもしくは読めない場合はnull
+     */
+    public JsObject loadDraftConfig(){
+        JsObject result = loadJsObject(DRAFT_FILE);
+        return result;
+    }
+
+    /**
+     * ネットワーク設定ファイルを読み込む。
+     * @return ネットワーク設定データ。
+     * 設定を読まないもしくは読めない場合はnull
+     */
+    public JsObject loadNetConfig(){
+        JsObject result = loadJsObject(NETCONFIG_FILE);
+        return result;
+    }
+
+    /**
+     * 台詞表示設定ファイルを読み込む。
+     * @return 台詞表示設定データ。
+     * 設定を読まないもしくは読めない場合はnull
+     */
+    public JsObject loadTalkConfig(){
+        JsObject result = loadJsObject(TALKCONFIG_FILE);
+        return result;
+    }
+
+    /**
+     * 検索履歴ファイルに書き込む。
+     * @param root 履歴データ
+     * @return 書き込まなかったもしくは書き込めなかった場合はfalse
+     */
+    public boolean saveHistoryConfig(JsComposition<?> root){
+        boolean result = saveJson(HIST_FILE, root);
+        return result;
+    }
+
+    /**
+     * 原稿ファイルに書き込む。
+     * @param root 原稿データ
+     * @return 書き込まなかったもしくは書き込めなかった場合はfalse
+     */
+    public boolean saveDraftConfig(JsComposition<?> root){
+        boolean result = saveJson(DRAFT_FILE, root);
+        return result;
+    }
+
+    /**
+     * ネットワーク設定ファイルに書き込む。
+     * @param root ネットワーク設定
+     * @return 書き込まなかったもしくは書き込めなかった場合はfalse
+     */
+    public boolean saveNetConfig(JsComposition<?> root){
+        boolean result = saveJson(NETCONFIG_FILE, root);
+        return result;
+    }
+
+    /**
+     * 台詞表示設定ファイルに書き込む。
+     * @param root 台詞表示設定
+     * @return 書き込まなかったもしくは書き込めなかった場合はfalse
+     */
+    public boolean saveTalkConfig(JsComposition<?> root){
+        boolean result = saveJson(TALKCONFIG_FILE, root);
+        return result;
+    }
+
+}
@@ -5,7 +5,7 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.config;
 
 import java.io.File;
 import java.text.NumberFormat;
@@ -29,6 +29,9 @@ public final class EnvInfo{
     /** Java実行形バージョン。 */
     public static final String JAVA_VERSION;
 
+    /** 最大ヒープメモリ。 */
+    public static final long MAX_MEMORY;
+
     private static final SortedMap<String, String> propertyMap =
             new TreeMap<String, String>();
 
@@ -47,12 +50,18 @@ public final class EnvInfo{
         getSecureEnvironment("LANG");
         getSecureEnvironment("DISPLAY");
 
+        Runtime runtime = Runtime.getRuntime();
+        MAX_MEMORY = runtime.maxMemory();
+
         String classpath   = getSecureProperty("java.class.path");
+        String[] pathVec;
         if(classpath != null){
-            classpaths = classpath.split(File.pathSeparator);
+            pathVec = classpath.split(File.pathSeparator);
         }else{
-            classpaths = new String[0];
+            pathVec = new String[0];
         }
+        classpaths = pathVec;
+
     }
 
 
@@ -104,15 +113,9 @@ public final class EnvInfo{
         StringBuilder result = new StringBuilder();
         NumberFormat nform = NumberFormat.getNumberInstance();
 
-        result.append("最大ヒープメモリ量: "
-                + nform.format(Jindolf.RUNTIME.maxMemory()) + " bytes\n");
-
-        result.append("\n");
-
-        result.append("起動時引数:\n");
-        for(String arg : Jindolf.getOptionInfo().getInvokeArgList()){
-            result.append("  ").append(arg).append("\n");
-        }
+        result.append("最大ヒープメモリ量: ")
+              .append(nform.format(MAX_MEMORY))
+              .append(" bytes\n");
 
         result.append("\n");
 
@@ -5,7 +5,7 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.config;
 
 import java.io.File;
 import java.lang.reflect.InvocationTargetException;
@@ -24,6 +24,8 @@ import java.util.Locale;
  */
 public final class FileUtils{
 
+    private static final Class<?> THISKLASS = FileUtils.class;
+
     private static final String SCHEME_FILE = "file";
 
     /** JRE1.6のjava.io.File#setReadableに相当。 */
@@ -66,6 +68,7 @@ public final class FileUtils{
         METHOD_SETWRITABLE = method;
 
         assert ! ( isMacOSXFs() && isWindowsOSFs() );
+        new FileUtils().hashCode();
     }
 
 
@@ -73,8 +76,9 @@ public final class FileUtils{
      * 隠しコンストラクタ。
      */
     private FileUtils(){
-        assert false;
-        throw new AssertionError();
+        super();
+        assert this.getClass() == THISKLASS;
+        return;
     }
 
 
@@ -284,6 +288,16 @@ public final class FileUtils{
     }
 
     /**
+     * このクラスがローカルJARファイルからロードされたのであれば
+     * その格納ディレクトリを返す。
+     * @return ロード元JARファイルの格納ディレクトリ。
+     * JARが見つからない、もしくはロード元がJARファイルでなければnull。
+     */
+    public static File getJarDirectory(){
+        return getJarDirectory(THISKLASS);
+    }
+
+    /**
      * ホームディレクトリを得る。
      * システムプロパティuser.homeで示されたホームディレクトリを返す。
      * @return ホームディレクトリ。何らかの事情でnullを返す場合もあり。
@@ -5,7 +5,7 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.config;
 
 import java.io.File;
 import java.io.FileNotFoundException;
diff --git a/src/main/java/jp/sfjp/jindolf/config/OptionInfo.java b/src/main/java/jp/sfjp/jindolf/config/OptionInfo.java
new file mode 100644 (file)
index 0000000..cf9211b
--- /dev/null
@@ -0,0 +1,340 @@
+/*
+ * option argument information
+ *
+ * License : The MIT License
+ * Copyright(c) 2009 olyutorskii
+ */
+
+package jp.sfjp.jindolf.config;
+
+import java.text.MessageFormat;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * コマンドラインオプション情報。
+ * public static void main()の引数から展開される。
+ */
+public class OptionInfo{
+
+    private static final Pattern PATTERN_GEOMETRY =
+            Pattern.compile(
+                 "([1-9][0-9]*)x([1-9][0-9]*)"
+                +"(?:(\\+|\\-)([1-9][0-9]*)(\\+|\\-)([1-9][0-9]*))?"
+                );
+
+    private static final String ERRFORM_UKNOWN =
+            "未定義の起動オプション[{0}]が指定されました。";
+    private static final String ERRFORM_NOARG =
+            "起動オプション[{0}]に引数がありません。";
+    private static final String ERRFORM_GEOM =
+              "起動オプション[{0}]のジオメトリ指定[{1}]が不正です。"
+            + "WIDTHxHEIGHT[(+|-)XPOS(+|-)YPOS]の形式で指定してください";
+    private static final String ERRFORM_BOOL =
+              "起動オプション[{0}]の真偽指定[{1}]が不正です。"
+            + "on, off, yes, no, true, falseのいずれかを指定してください。";
+    private static final String ERRFORM_NONBOOL =
+            "起動オプション[{0}]は真偽を指定するオプションではありません。";
+
+
+    private Integer frameWidth  = null;
+    private Integer frameHeight = null;
+    private Integer frameXpos = null;
+    private Integer frameYpos = null;
+
+    private final List<String> invokeArgs = new LinkedList<String>();
+    private final List<CmdOption> optionList = new LinkedList<CmdOption>();
+    private final Map<CmdOption, Boolean> boolOptionMap =
+            new EnumMap<CmdOption, Boolean>(CmdOption.class);
+    private final Map<CmdOption, String> stringOptionMap =
+            new EnumMap<CmdOption, String>(CmdOption.class);
+
+
+    /**
+     * コンストラクタ。
+     */
+    protected OptionInfo(){
+        super();
+        return;
+    }
+
+
+    /**
+     * 文字列が可変引数のいずれかと英字大小無視で等しいか判定する。
+     * <p>※ JRE1.6のString#equalsIgnoreCase の代替も兼ねる。
+     * @param text 文字列
+     * @param names 文字列の可変引数
+     * @return 等しい物があればtrue
+     */
+    private static boolean equalsIgnoreCase(String text, String ... names){
+        for(String name : names){
+            if(text.compareToIgnoreCase(name) == 0) return true;
+        }
+        return false;
+    }
+
+    /**
+     * 真偽二値をとるオプション解析の下請け。
+     * @param info オプション情報格納先
+     * @param option オプション種別
+     * @param optTxt オプション名文字列
+     * @param onoff オプション引数
+     * @throws IllegalArgumentException 構文エラー
+     */
+    private static void parseBooleanSwitch(OptionInfo info,
+                                           CmdOption option,
+                                           String optTxt,
+                                           String onoff )
+            throws IllegalArgumentException{
+        Boolean flag;
+
+        if(equalsIgnoreCase(onoff, "on", "yes", "true")){
+            flag = Boolean.TRUE;
+        }else if(equalsIgnoreCase(onoff, "off", "no", "false")){
+            flag = Boolean.FALSE;
+        }else{
+            String errmsg =
+                    MessageFormat.format(ERRFORM_BOOL, optTxt, onoff);
+            throw new IllegalArgumentException(errmsg);
+        }
+
+        info.boolOptionMap.put(option, flag);
+
+        return;
+    }
+
+    /**
+     * 文字列がマイナス記号で始まるか判定する。
+     * @param txt 文字列
+     * @return マイナス記号で始まればtrue
+     */
+    private static boolean isMinus(String txt){
+        if(txt.length() >= 1 && txt.charAt(0) == '-'){
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * ウィンドウジオメトリオプション解析。
+     * <p>例) WIDTHxHEIGHT+XPOS+YPOS
+     * @param info オプション情報格納先
+     * @param optTxt オプション名文字列
+     * @param geometry オプション引数
+     * @throws IllegalArgumentException 構文エラー
+     */
+    private static void parseGeometry(OptionInfo info,
+                                      String optTxt,
+                                      String geometry )
+            throws IllegalArgumentException{
+        Matcher matcher = PATTERN_GEOMETRY.matcher(geometry);
+        if( ! matcher.matches() ){
+            String errmsg = MessageFormat.format(ERRFORM_GEOM,
+                                                 optTxt, geometry);
+            throw new IllegalArgumentException(errmsg);
+        }
+
+        int gpos = 1;
+        String width  = matcher.group(gpos++);
+        String height = matcher.group(gpos++);
+        String xSign  = matcher.group(gpos++);
+        String xPos   = matcher.group(gpos++);
+        String ySign  = matcher.group(gpos++);
+        String yPos   = matcher.group(gpos++);
+
+        info.frameWidth  = Integer.parseInt(width);
+        info.frameHeight = Integer.parseInt(height);
+
+        if(xPos != null){
+            info.frameXpos = Integer.parseInt(xPos);
+            if(isMinus(xSign)){
+                info.frameXpos = -info.frameXpos;
+            }
+        }
+
+        if(yPos != null){
+            info.frameYpos = Integer.parseInt(yPos);
+            if(isMinus(ySign)){
+                info.frameYpos = -info.frameYpos;
+            }
+        }
+
+        return;
+    }
+
+    /**
+     * オプション引数を解析する。
+     * @param result オプション情報
+     * @param optTxt オプション文字列
+     * @param option オプション種別
+     * @param iterator オプション並び
+     * @return 引数と同じオプション情報
+     * @throws IllegalArgumentException 構文エラー
+     */
+    private static OptionInfo parseOptionArg(OptionInfo result,
+                                             String optTxt,
+                                             CmdOption option,
+                                             Iterator<String> iterator )
+            throws IllegalArgumentException {
+        String nextArg;
+        if(iterator.hasNext()){
+            nextArg = iterator.next();
+        }else{
+            String errMsg = MessageFormat.format(ERRFORM_NOARG, optTxt);
+            throw new IllegalArgumentException(errMsg);
+        }
+
+        if(option == CmdOption.OPT_GEOMETRY){
+            parseGeometry(result, optTxt, nextArg);
+        }else if(option.isBooleanOption()){
+            parseBooleanSwitch(result, option, optTxt, nextArg);
+        }else if(   option == CmdOption.OPT_INITFONT
+                 || option == CmdOption.OPT_CONFDIR ){
+            result.stringOptionMap.put(option, nextArg);
+        }else{
+            assert false;
+        }
+
+        return result;
+    }
+
+    /**
+     * オプション文字列を解析する。
+     * @param args main()に渡されるオプション文字列
+     * @return 解析済みのオプション情報。
+     * @throws IllegalArgumentException 構文エラー
+     */
+    public static OptionInfo parseOptions(String ... args)
+            throws IllegalArgumentException{
+        OptionInfo result = new OptionInfo();
+
+        for(String arg : args){
+            if(arg == null) continue;
+            result.invokeArgs.add(arg);
+        }
+        Iterator<String> iterator = result.invokeArgs.iterator();
+
+        while(iterator.hasNext()){
+            String arg = iterator.next();
+
+            CmdOption option = CmdOption.parseCmdOption(arg);
+            if(option == null){
+                String errmsg = MessageFormat.format(ERRFORM_UKNOWN, arg);
+                throw new IllegalArgumentException(errmsg);
+            }
+            result.optionList.add(option);
+
+            if( ! option.isIndepOption() ){
+                parseOptionArg(result, arg, option, iterator);
+            }
+        }
+
+        return result;
+    }
+
+
+    /**
+     * 全引数のリストを返す。
+     * @return 全引数のリスト
+     */
+    public List<String> getInvokeArgList(){
+        return Collections.unmodifiableList(this.invokeArgs);
+    }
+
+    /**
+     * オプションが指定されていたか否か判定する。
+     * @param option オプション
+     * @return 指定されていたらtrue
+     */
+    public boolean hasOption(CmdOption option){
+        if(this.optionList.contains(option)) return true;
+        return false;
+    }
+
+    /**
+     * 真偽値をとるオプション値を返す。
+     * 複数回指定された場合は最後の値。
+     * @param option オプション
+     * @return 真偽値。オプション指定がなかった場合はnull
+     * @throws IllegalArgumentException 真偽値を取るオプションではない。
+     */
+    public Boolean getBooleanArg(CmdOption option)
+            throws IllegalArgumentException{
+        if( ! option.isBooleanOption() ){
+            String errMsg =
+                    MessageFormat.format(ERRFORM_NONBOOL, option.toString());
+            throw new IllegalArgumentException(errMsg);
+        }
+        Boolean result = this.boolOptionMap.get(option);
+        return result;
+    }
+
+    /**
+     * 文字列引数をとるオプション値を返す。
+     * 複数回指定された場合は最後の値。
+     * @param option オプション
+     * @return 文字列。オプション指定がなかった場合はnull
+     */
+    public String getStringArg(CmdOption option){
+        String result = this.stringOptionMap.get(option);
+        return result;
+    }
+
+    /**
+     * 排他的オプションのいずれかが指定されたか判定する。
+     * 後から指定された方が有効となる。
+     * @param options 排他的オプション群
+     * @return いずれかのオプション。どれも指定されなければnull
+     */
+    public CmdOption getExclusiveOption(CmdOption... options){
+        CmdOption result = null;
+        for(CmdOption option : this.optionList){
+            for(CmdOption excOption : options){
+                if(option == excOption){
+                    result = option;
+                    break;
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 初期のフレーム幅を返す。
+     * @return 初期のフレーム幅。オプション指定されてなければnull
+     */
+    public Integer initialFrameWidth(){
+        return this.frameWidth;
+    }
+
+    /**
+     * 初期のフレーム高を返す。
+     * @return 初期のフレーム高。オプション指定されてなければnull
+     */
+    public Integer initialFrameHeight(){
+        return this.frameHeight;
+    }
+
+    /**
+     * 初期のフレーム位置のX座標を返す。
+     * @return 初期のフレーム位置のX座標。オプション指定されてなければnull
+     */
+    public Integer initialFrameXpos(){
+        return this.frameXpos;
+    }
+
+    /**
+     * 初期のフレーム位置のY座標を返す。
+     * @return 初期のフレーム位置のY座標。オプション指定されてなければnull
+     */
+    public Integer initialFrameYpos(){
+        return this.frameYpos;
+    }
+
+}
diff --git a/src/main/java/jp/sfjp/jindolf/config/package-info.java b/src/main/java/jp/sfjp/jindolf/config/package-info.java
new file mode 100644 (file)
index 0000000..fc15f1f
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * パッケージ情報
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 olyutorskii
+ */
+
+/**
+ * 各種アプリ設定や実行環境に関する一連のクラス。
+ */
+
+package jp.sfjp.jindolf.config;
+
+/* EOF */
@@ -5,12 +5,13 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.data;
 
 import java.util.LinkedList;
 import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import jp.sfjp.jindolf.util.StringUtils;
 
 /**
  * 発言アンカー。
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.data;
 
 import java.io.IOException;
 import java.net.URISyntaxException;
@@ -19,6 +19,7 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.ParserConfigurationException;
+import jp.sfjp.jindolf.dxchg.XmlUtils;
 import jp.sourceforge.jindolf.corelib.PreDefAvatar;
 import org.xml.sax.SAXException;
 
@@ -5,7 +5,7 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.data;
 
 /**
  * 発言表示設定。
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.data;
 
 import java.awt.image.BufferedImage;
 import java.io.IOException;
@@ -18,6 +18,9 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.SortedSet;
 import java.util.TreeSet;
+import jp.sfjp.jindolf.log.LogWrapper;
+import jp.sfjp.jindolf.net.HtmlSequence;
+import jp.sfjp.jindolf.net.ServerAccess;
 import jp.sourceforge.jindolf.corelib.LandDef;
 import jp.sourceforge.jindolf.corelib.LandState;
 import jp.sourceforge.jindolf.corelib.VillageState;
@@ -36,6 +39,8 @@ public class Land {
     // 古国ID
     private static final String ID_VANILLAWOLF = "wolf";
 
+    private static final LogWrapper LOGGER = new LogWrapper();
+
 
     private final LandDef landDef;
     private final ServerAccess serverAccess;
@@ -117,7 +122,7 @@ public class Land {
         try{
             uri = new URI(pureHREF);
         }catch(URISyntaxException e){
-            Jindolf.logger().warn(
+            LOGGER.warn(
                      "不正なURI["
                     + hrefValue
                     + "]を検出しました");
@@ -204,7 +209,7 @@ public class Land {
         try{
             image = server.downloadImage(imageURL);
         }catch(IOException e){
-            Jindolf.logger().warn(
+            LOGGER.warn(
                     "イメージ[" + imageURL + "]"
                     + "のダウンロードに失敗しました",
                     e );
@@ -258,7 +263,7 @@ public class Land {
             try{
                 this.parser.parseAutomatic(content);
             }catch(HtmlParseException e){
-                Jindolf.logger().warn("トップページを認識できない", e);
+                LOGGER.warn("トップページを認識できない", e);
             }
             List<Village> list = this.handler.getVillageList();
             if(list != null){
@@ -273,7 +278,7 @@ public class Land {
             try{
                 this.parser.parseAutomatic(content);
             }catch(HtmlParseException e){
-                Jindolf.logger().warn("村一覧ページを認識できない", e);
+                LOGGER.warn("村一覧ページを認識できない", e);
             }
             List<Village> list = this.handler.getVillageList();
             if(list != null){
@@ -396,7 +401,7 @@ public class Land {
             String villageID = getVillageIDFromHREF(href);
             if(   villageID == null
                || villageID.length() <= 0 ){
-                Jindolf.logger().warn(
+                LOGGER.warn(
                         "認識できないURL[" + href + "]に遭遇しました。");
                  return;
             }
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.data;
 
 import java.io.IOException;
 import java.net.URISyntaxException;
@@ -21,6 +21,8 @@ import javax.swing.tree.TreeModel;
 import javax.swing.tree.TreePath;
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.ParserConfigurationException;
+import jp.sfjp.jindolf.dxchg.XmlUtils;
+import jp.sfjp.jindolf.log.LogWrapper;
 import jp.sourceforge.jindolf.corelib.LandDef;
 import org.xml.sax.SAXException;
 
@@ -34,6 +36,8 @@ public class LandsModel implements TreeModel{ // ComboBoxModelも付けるか?
     private static final String ROOT = "ROOT";
     private static final int SECTION_INTERVAL = 100;
 
+    private static final LogWrapper LOGGER = new LogWrapper();
+
 
     private final List<Land> landList = new LinkedList<Land>();
     private final List<Land> unmodList =
@@ -98,16 +102,16 @@ public class LandsModel implements TreeModel{ // ComboBoxModelも付けるか?
             DocumentBuilder builder = XmlUtils.createDocumentBuilder();
             landDefList = LandDef.buildLandDefList(builder);
         }catch(IOException e){
-            Jindolf.logger().fatal("failed to load land list", e);
+            LOGGER.fatal("failed to load land list", e);
             return;
         }catch(SAXException e){
-            Jindolf.logger().fatal("failed to load land list", e);
+            LOGGER.fatal("failed to load land list", e);
             return;
         }catch(URISyntaxException e){
-            Jindolf.logger().fatal("failed to load land list", e);
+            LOGGER.fatal("failed to load land list", e);
             return;
         }catch(ParserConfigurationException e){
-            Jindolf.logger().fatal("failed to load land list", e);
+            LOGGER.fatal("failed to load land list", e);
             return;
         }
 
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.data;
 
 import java.io.IOException;
 import java.util.Collections;
@@ -15,6 +15,10 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import jp.sfjp.jindolf.log.LogWrapper;
+import jp.sfjp.jindolf.net.HtmlSequence;
+import jp.sfjp.jindolf.net.ServerAccess;
+import jp.sfjp.jindolf.util.StringUtils;
 import jp.sourceforge.jindolf.corelib.EventFamily;
 import jp.sourceforge.jindolf.corelib.GameRole;
 import jp.sourceforge.jindolf.corelib.LandDef;
@@ -46,6 +50,8 @@ public class Period{
     private static final PeriodHandler HANDLER =
             new PeriodHandler();
 
+    private static final LogWrapper LOGGER = new LogWrapper();
+
     static{
         PARSER.setBasicHandler   (HANDLER);
         PARSER.setSysEventHandler(HANDLER);
@@ -170,7 +176,7 @@ public class Period{
         try{
             PARSER.parseAutomatic(content);
         }catch(HtmlParseException e){
-            Jindolf.logger().warn("発言抽出に失敗", e);
+            LOGGER.warn("発言抽出に失敗", e);
         }
 
         if(wasHot && ! period.isHot() ){
@@ -5,7 +5,7 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.data;
 
 import jp.sourceforge.jindolf.corelib.Destiny;
 import jp.sourceforge.jindolf.corelib.GameRole;
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.data;
 
 import java.util.regex.Pattern;
 import java.util.regex.PatternSyntaxException;
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.data;
 
 import java.util.Collections;
 import java.util.HashSet;
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.data;
 
 import jp.sourceforge.jindolf.corelib.TalkType;
 
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.data;
 
 /**
  * プレイヤーの発言及びゲームシステムからのメッセージのスーパーインタフェース。
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.data;
 
 import java.awt.image.BufferedImage;
 import java.io.IOException;
@@ -16,6 +16,10 @@ import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import jp.sfjp.jindolf.log.LogWrapper;
+import jp.sfjp.jindolf.net.HtmlSequence;
+import jp.sfjp.jindolf.net.ServerAccess;
+import jp.sfjp.jindolf.util.GUIUtils;
 import jp.sourceforge.jindolf.corelib.LandDef;
 import jp.sourceforge.jindolf.corelib.LandState;
 import jp.sourceforge.jindolf.corelib.PeriodType;
@@ -73,6 +77,8 @@ public class Village implements Comparable<Village> {
     private static final VillageHeadHandler HANDLER =
             new VillageHeadHandler();
 
+    private static final LogWrapper LOGGER = new LogWrapper();
+
     static{
         PARSER.setBasicHandler   (HANDLER);
         PARSER.setSysEventHandler(HANDLER);
@@ -163,7 +169,7 @@ public class Village implements Comparable<Village> {
         try{
             PARSER.parseAutomatic(content);
         }catch(HtmlParseException e){
-            Jindolf.logger().warn("村の状態が不明", e);
+            LOGGER.warn("村の状態が不明", e);
         }
 
         return;
@@ -794,7 +800,7 @@ public class Village implements Comparable<Village> {
             if(villageState == VillageState.UNKNOWN){
                 this.village.setState(villageState);
                 this.village.periodList.clear();
-                Jindolf.logger().warn("村の状況を読み取れません");
+                LOGGER.warn("村の状況を読み取れません");
                 return;
             }
 
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.dxchg;
 
 import java.awt.Toolkit;
 import java.awt.datatransfer.Clipboard;
@@ -5,7 +5,7 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.dxchg;
 
 import java.awt.Component;
 import java.awt.GridBagConstraints;
@@ -31,6 +31,12 @@ import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.border.Border;
 import javax.swing.filechooser.FileFilter;
+import jp.sfjp.jindolf.data.Avatar;
+import jp.sfjp.jindolf.data.Period;
+import jp.sfjp.jindolf.data.Talk;
+import jp.sfjp.jindolf.data.Topic;
+import jp.sfjp.jindolf.data.Village;
+import jp.sfjp.jindolf.view.TopicFilter;
 import jp.sourceforge.jindolf.corelib.TalkType;
 
 /**
@@ -5,10 +5,11 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.dxchg;
 
 import java.util.HashMap;
 import java.util.Map;
+import jp.sfjp.jindolf.data.Avatar;
 
 /**
  * 顔アイコンWiki表記のセット。
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.dxchg;
 
 import java.awt.Component;
 import java.beans.PropertyChangeEvent;
@@ -5,7 +5,7 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.dxchg;
 
 import java.awt.datatransfer.DataFlavor;
 import java.awt.datatransfer.Transferable;
@@ -5,7 +5,7 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.dxchg;
 
 import java.awt.Frame;
 import java.awt.GridBagConstraints;
@@ -21,6 +21,8 @@ import javax.swing.JButton;
 import javax.swing.JLabel;
 import javax.swing.JPanel;
 import javax.swing.SwingUtilities;
+import jp.sfjp.jindolf.util.GUIUtils;
+import jp.sfjp.jindolf.util.Monodizer;
 
 /**
  * Webブラウザ起動ボタン。
@@ -5,7 +5,7 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.dxchg;
 
 import java.awt.GraphicsEnvironment;
 import java.awt.HeadlessException;
@@ -5,7 +5,7 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.dxchg;
 
 import java.awt.Container;
 import java.awt.Frame;
@@ -35,6 +35,11 @@ import javax.swing.SwingConstants;
 import javax.swing.TransferHandler;
 import javax.swing.border.Border;
 import javax.swing.border.EtchedBorder;
+import jp.sfjp.jindolf.JreChecker;
+import jp.sfjp.jindolf.VerInfo;
+import jp.sfjp.jindolf.log.LogWrapper;
+import jp.sfjp.jindolf.util.GUIUtils;
+import jp.sfjp.jindolf.util.Monodizer;
 
 /**
  * Webブラウザ起動用の専用ダイアログ。
@@ -44,8 +49,11 @@ public class WebIPCDialog
         extends JDialog
         implements ActionListener {
 
-    private static final String FRAMETITLE =
-            "URLへのアクセス確認 - " + Jindolf.TITLE;
+    private static final String TITLE_WWW =
+            VerInfo.getFrameTitle("URLへのアクセス確認");
+
+
+    private static final LogWrapper LOGGER = new LogWrapper();
 
 
     private final String warnMessage;
@@ -73,7 +81,8 @@ public class WebIPCDialog
      * @param owner オーナーフレーム
      */
     public WebIPCDialog(Frame owner){
-        super(owner, FRAMETITLE, true);
+        super(owner);
+        setModal(true);
 
         GUIUtils.modifyWindowAttributes(this, true, false, true);
 
@@ -87,7 +96,7 @@ public class WebIPCDialog
         this.ipc = webipc;
 
         if(this.ipc == null){
-            if( ! Jindolf.JRE_PACKAGE.isCompatibleWith("1.6") ){
+            if( ! JreChecker.has16Runtime() ){
                 this.warnMessage =
                         "この機能を利用するには、JRE1.6以上が必要です";
             }else{
@@ -141,7 +150,6 @@ public class WebIPCDialog
         return;
     }
 
-
     /**
      * Webブラウザ起動用のモーダルダイアログを表示する。
      * @param owner オーナーフレーム
@@ -150,6 +158,7 @@ public class WebIPCDialog
     public static void showDialog(Frame owner, String url){
         WebIPCDialog dialog = new WebIPCDialog(owner);
 
+        dialog.setTitle(TITLE_WWW);
         dialog.setUrlText(url);
         dialog.pack();
         dialog.setLocationRelativeTo(owner);
@@ -285,7 +294,7 @@ public class WebIPCDialog
 
         if(this.ipc == null){
             String title;
-            if( ! Jindolf.JRE_PACKAGE.isCompatibleWith("1.6") ){
+            if( ! JreChecker.has16Runtime() ){
                 title = "新しいJavaを入手しましょう";
             }else{
                 title = "報告";
@@ -314,7 +323,7 @@ public class WebIPCDialog
             String logmsg =   "URL "
                             + this.uri.toASCIIString()
                             + " へのアクセスをWebブラウザに指示しました";
-            Jindolf.logger().info(logmsg);
+            LOGGER.info(logmsg);
         }finally{
             close();
         }
@@ -338,7 +347,7 @@ public class WebIPCDialog
             String logmsg =  "文字列「"
                            + uristring
                            + "」をクリップボードにコピーしました";
-            Jindolf.logger().info(logmsg);
+            LOGGER.info(logmsg);
         }finally{
             close();
         }
@@ -415,7 +424,7 @@ public class WebIPCDialog
             String logmsg =   "URL "
                             + WebIPCDialog.this.uri.toASCIIString()
                             + " がどこかへドラッグ&ドロップされました";
-            Jindolf.logger().info(logmsg);
+            LOGGER.info(logmsg);
 
             close();
 
@@ -5,10 +5,10 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.dxchg;
 
+import java.io.FileNotFoundException;
 import java.io.IOException;
-import java.io.InputStream;
 import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
 import java.nio.charset.CharacterCodingException;
@@ -24,6 +24,9 @@ import java.util.SortedSet;
 import java.util.TreeSet;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import jp.sfjp.jindolf.ResourceManager;
+import jp.sfjp.jindolf.data.Avatar;
+import jp.sfjp.jindolf.log.LogWrapper;
 import jp.sourceforge.jindolf.corelib.Destiny;
 import jp.sourceforge.jindolf.corelib.GameRole;
 
@@ -43,7 +46,7 @@ public final class WolfBBS{
             Pattern.compile("[A-Z][a-z]+([A-Z])[a-z]+");
 
     private static final String FACEICONSET =
-            "resources/faceIconSet.properties";
+            "resources/wolfbbs/faceIconSet.properties";
     private static final String ORDER_PREFIX = "iconset.order.";
     private static final List<FaceIconSet> FACEICONSET_LIST =
             new LinkedList<FaceIconSet>();
@@ -52,8 +55,14 @@ public final class WolfBBS{
 
     private static final String WOLFBBS_URL = "http://wolfbbs.jp/";
 
+    private static final LogWrapper LOGGER = new LogWrapper();
+
     static{
-        loadFaceIconSet();
+        try{
+            loadFaceIconSet();
+        }catch(FileNotFoundException e){
+            throw new ExceptionInInitializerError(e);
+        }
 
         StringBuilder wikicomment = new StringBuilder();
         wikicomment.append("// ");
@@ -76,17 +85,13 @@ public final class WolfBBS{
 
     /**
      * アイコンセットのロード。
+     * @throws FileNotFoundException リソースが不明
      */
-    private static void loadFaceIconSet(){
-        InputStream is = Jindolf.getResourceAsStream(FACEICONSET);
-        Properties properties = new Properties();
-        try{
-            properties.load(is);
-            is.close();
-        }catch(IOException e){
-            Jindolf.logger().fatal(
-                    "顔アイコンセットの読み込みに失敗しました", e);
-            Jindolf.exit(1);
+    private static void loadFaceIconSet() throws FileNotFoundException {
+        Properties properties = ResourceManager.getProperties(FACEICONSET);
+        if(properties == null){
+            LOGGER.fatal("顔アイコンセットの読み込みに失敗しました");
+            throw new FileNotFoundException();
         }
 
         loadFaceIconSet(properties);
@@ -97,18 +102,19 @@ public final class WolfBBS{
     /**
      * アイコンセットのロード。
      * @param properties プロパティ
+     * @throws FileNotFoundException リソースが不明
      */
-    private static void loadFaceIconSet(Properties properties){
+    private static void loadFaceIconSet(Properties properties)
+            throws FileNotFoundException {
         String codeCheck = properties.getProperty("codeCheck");
         if(   codeCheck == null
            || codeCheck.length() != 1
            || codeCheck.charAt(0) != '\u72fc'){  // 「狼」
-            Jindolf.logger().fatal(
+            LOGGER.fatal(
                     "顔アイコンセットプロパティファイルの"
                     +"文字コードがおかしいようです。"
                     +"native2ascii は正しく適用しましたか?");
-            Jindolf.exit(1);
-            return;
+            throw new FileNotFoundException();
         }
 
         Set<Object> keySet = properties.keySet();
@@ -285,7 +291,6 @@ public final class WolfBBS{
 
         int resultLength;
 
-        resultLength = result.length();
         while(result.length() > 0 && result.charAt(0) == '/'){
             result.deleteCharAt(0);
         }
@@ -5,7 +5,7 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.dxchg;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -5,7 +5,7 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.dxchg;
 
 import java.io.IOException;
 import java.io.InputStream;
diff --git a/src/main/java/jp/sfjp/jindolf/dxchg/package-info.java b/src/main/java/jp/sfjp/jindolf/dxchg/package-info.java
new file mode 100644 (file)
index 0000000..8307f4d
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * パッケージ情報
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 olyutorskii
+ */
+
+/**
+ * 他アプリやサービスとのデータ交換関連の一連のクラス。
+ */
+
+package jp.sfjp.jindolf.dxchg;
+
+/* EOF */
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.editor;
 
 import java.awt.BorderLayout;
 import java.awt.Color;
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.editor;
 
 import java.awt.Dimension;
 import java.awt.EventQueue;
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.editor;
 
 import java.awt.Color;
 import java.awt.Dimension;
@@ -29,6 +29,7 @@ import javax.swing.text.Document;
 import javax.swing.text.NavigationFilter;
 import javax.swing.text.PlainDocument;
 import javax.swing.text.Segment;
+import jp.sfjp.jindolf.dxchg.TextPopup;
 
 /**
  * 原稿作成支援エディタ。
@@ -74,6 +75,7 @@ public class TalkEditor
      * コンストラクタ。
      * @param seqNumber 通し番号
      */
+    @SuppressWarnings("LeakingThisInConstructor")
     private TalkEditor(int seqNumber){
         super();
 
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.editor;
 
 import java.awt.BorderLayout;
 import java.awt.Color;
@@ -35,9 +35,15 @@ import javax.swing.border.TitledBorder;
 import javax.swing.event.ChangeEvent;
 import javax.swing.event.ChangeListener;
 import javax.swing.text.JTextComponent;
+import jp.sfjp.jindolf.dxchg.ClipboardAction;
+import jp.sfjp.jindolf.dxchg.TextPopup;
+import jp.sfjp.jindolf.glyph.FontInfo;
+import jp.sfjp.jindolf.util.GUIUtils;
 import jp.sourceforge.jovsonz.JsArray;
+import jp.sourceforge.jovsonz.JsComposition;
 import jp.sourceforge.jovsonz.JsObject;
 import jp.sourceforge.jovsonz.JsString;
+import jp.sourceforge.jovsonz.JsTypes;
 import jp.sourceforge.jovsonz.JsValue;
 
 /**
@@ -47,9 +53,10 @@ import jp.sourceforge.jovsonz.JsValue;
 public class TalkPreview extends JFrame
         implements ActionListener, ChangeListener{
 
-    private static final String FRAMETITLE = "発言エディタ - " + Jindolf.TITLE;
+    /** 原稿ファイル。 */
+    public static final File DRAFT_FILE = new File("draft.json");
+
     private static final Color COLOR_EDITORBACK = Color.BLACK;
-    private static final String DRAFT_FILE = "draft.json";
 
     private final JTextComponent freeMemo = new TextEditor();
 
@@ -74,8 +81,9 @@ public class TalkPreview extends JFrame
     /**
      * コンストラクタ。
      */
+    @SuppressWarnings("LeakingThisInConstructor")
     public TalkPreview(){
-        super(FRAMETITLE);
+        super();
 
         GUIUtils.modifyWindowAttributes(this, true, false, true);
 
@@ -338,28 +346,44 @@ public class TalkPreview extends JFrame
     }
 
     /**
-     * 原稿のロード。
+     * JSON形式の原稿情報を返す。
+     * @return JSON形式の原稿情報
+     */
+    public JsObject getJson(){
+        JsObject result = new JsObject();
+        JsString memo = new JsString(this.freeMemo.getText());
+        result.putValue("freeMemo", memo);
+
+        JsArray array = new JsArray();
+        JsString text = new JsString(this.editArray.getAllText());
+        array.add(text);
+        result.putValue("drafts", array);
+
+        return result;
+    }
+
+    /**
+     * JSON形式の原稿情報を反映させる。
+     * @param root JSON形式の原稿情報。nullが来たら何もしない
      */
-    public void loadDraft(){
-        JsValue value = ConfigFile.loadJson(new File(DRAFT_FILE));
-        if(value == null) return;
+    public void putJson(JsObject root){
+        if(root == null) return;
 
-        if( ! (value instanceof JsObject) ) return;
-        JsObject root = (JsObject) value;
+        JsValue value;
 
         value = root.getValue("freeMemo");
-        if(value instanceof JsString){
+        if(value.getJsTypes() == JsTypes.STRING){
             JsString memo = (JsString) value;
             this.freeMemo.setText(memo.toRawString());
         }
 
         value = root.getValue("drafts");
-        if( ! (value instanceof JsArray) ) return;
+        if(value.getJsTypes() != JsTypes.ARRAY) return;
         JsArray array = (JsArray) value;
 
         StringBuilder draftAll = new StringBuilder();
         for(JsValue elem : array){
-            if( ! (elem instanceof JsString) ) continue;
+            if(elem.getJsTypes() != JsTypes.STRING) continue;
             JsString draft = (JsString) elem;
             draftAll.append(draft.toRawString());
         }
@@ -372,30 +396,15 @@ public class TalkPreview extends JFrame
     }
 
     /**
-     * 原稿のセーブ。
+     * 起動時の原稿と等価か判定する。
+     * @param conf 比較対象
+     * @return 等価ならtrue
      */
-    public void saveDraft(){
-        AppSetting setting = Jindolf.getAppSetting();
-        if( ! setting.useConfigPath() ) return;
-        File configPath = setting.getConfigPath();
-        if(configPath == null) return;
-
-        JsObject root = new JsObject();
-        JsString memo = new JsString(this.freeMemo.getText());
-        root.putValue("freeMemo", memo);
-
-        JsArray array = new JsArray();
-        JsString text = new JsString(this.editArray.getAllText());
-        array.add(text);
-        root.putValue("drafts", array);
-
+    public boolean hasConfChanged(JsComposition<?> conf){
         if(this.loadedDraft != null){
-            if(this.loadedDraft.equals(root)) return;
+            if(this.loadedDraft.equals(conf)) return true;
         }
-
-        ConfigFile.saveJson(new File(DRAFT_FILE), root);
-
-        return;
+        return false;
     }
 
     /**
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.editor;
 
 import java.awt.Rectangle;
 import java.awt.event.InputMethodEvent;
@@ -37,6 +37,7 @@ public class TextEditor extends JTextArea
     /**
      * コンストラクタ。
      */
+    @SuppressWarnings("LeakingThisInConstructor")
     public TextEditor(){
         super();
 
diff --git a/src/main/java/jp/sfjp/jindolf/editor/package-info.java b/src/main/java/jp/sfjp/jindolf/editor/package-info.java
new file mode 100644 (file)
index 0000000..0ba1728
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * パッケージ情報
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 olyutorskii
+ */
+
+/**
+ * 発言エディタ関連の一連のクラス。
+ */
+
+package jp.sfjp.jindolf.editor;
+
+/* EOF */
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.glyph;
 
 import java.awt.Rectangle;
 import java.awt.font.GlyphVector;
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.glyph;
 
 import java.awt.BasicStroke;
 import java.awt.Color;
@@ -16,6 +16,11 @@ import java.awt.Stroke;
 import java.awt.image.BufferedImage;
 import java.io.IOException;
 import java.util.regex.Pattern;
+import jp.sfjp.jindolf.data.Avatar;
+import jp.sfjp.jindolf.data.DialogPref;
+import jp.sfjp.jindolf.data.Period;
+import jp.sfjp.jindolf.data.Talk;
+import jp.sfjp.jindolf.data.Village;
 
 /**
  * アンカー描画。
@@ -5,10 +5,11 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.glyph;
 
 import java.awt.Point;
 import java.util.EventObject;
+import jp.sfjp.jindolf.data.Anchor;
 
 /**
  * 発言アンカーがクリックされたときのイベント。
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.glyph;
 
 import java.util.EventListener;
 
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.glyph;
 
 import java.awt.Color;
 import java.awt.Component;
@@ -42,6 +42,18 @@ import javax.swing.SwingConstants;
 import javax.swing.event.EventListenerList;
 import javax.swing.event.MouseInputListener;
 import javax.swing.text.DefaultEditorKit;
+import jp.sfjp.jindolf.data.Anchor;
+import jp.sfjp.jindolf.data.Avatar;
+import jp.sfjp.jindolf.data.DialogPref;
+import jp.sfjp.jindolf.data.Period;
+import jp.sfjp.jindolf.data.RegexPattern;
+import jp.sfjp.jindolf.data.SysEvent;
+import jp.sfjp.jindolf.data.Talk;
+import jp.sfjp.jindolf.data.Topic;
+import jp.sfjp.jindolf.dxchg.ClipboardAction;
+import jp.sfjp.jindolf.util.GUIUtils;
+import jp.sfjp.jindolf.view.ActionManager;
+import jp.sfjp.jindolf.view.TopicFilter;
 
 /**
  * 発言表示画面。
@@ -89,6 +101,7 @@ public class Discussion extends JComponent
     /**
      * 発言表示画面を作成する。
      */
+    @SuppressWarnings("LeakingThisInConstructor")
     public Discussion(){
         super();
 
@@ -5,15 +5,11 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.glyph;
 
 import java.awt.BorderLayout;
-import java.awt.Color;
 import java.awt.Container;
-import java.awt.Dimension;
 import java.awt.Font;
-import java.awt.Graphics;
-import java.awt.Graphics2D;
 import java.awt.GridBagConstraints;
 import java.awt.GridBagLayout;
 import java.awt.Insets;
@@ -22,24 +18,22 @@ import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.ItemEvent;
 import java.awt.event.ItemListener;
-import java.awt.font.FontRenderContext;
-import java.awt.geom.AffineTransform;
-import java.awt.geom.Rectangle2D;
-import java.io.IOException;
+import java.text.MessageFormat;
 import javax.swing.BorderFactory;
 import javax.swing.JButton;
 import javax.swing.JCheckBox;
 import javax.swing.JComboBox;
-import javax.swing.JComponent;
 import javax.swing.JLabel;
-import javax.swing.JList;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
 import javax.swing.JTextField;
-import javax.swing.ListSelectionModel;
 import javax.swing.border.Border;
 import javax.swing.event.ListSelectionEvent;
 import javax.swing.event.ListSelectionListener;
+import jp.sfjp.jindolf.ResourceManager;
+import jp.sfjp.jindolf.dxchg.TextPopup;
+import jp.sfjp.jindolf.log.LogWrapper;
+import jp.sfjp.jindolf.util.Monodizer;
 
 /**
  * 発言表示フォント選択パネル。
@@ -48,27 +42,25 @@ import javax.swing.event.ListSelectionListener;
 public class FontChooser extends JPanel
         implements ListSelectionListener,
                    ActionListener,
-                   ItemListener{
+                   ItemListener {
 
-    private static final Integer[] POINT_SIZES = {
+    private static final int[] POINT_SIZES = {
         8, 10, 12, 16, 18, 24, 32, 36, 48, 72,  // TODO これで十分?
     };
     private static final CharSequence PREVIEW_CONTENT;
+    private static final int UNIT_INC = 8;
+
+    private static final LogWrapper LOGGER = new LogWrapper();
 
     static{
-        CharSequence resourceText;
-        try{
-            resourceText = Jindolf.loadResourceText("resources/preview.txt");
-        }catch(IOException e){
-            resourceText = "ABC";
-        }
-        PREVIEW_CONTENT = resourceText;
+        PREVIEW_CONTENT =
+                ResourceManager.getTextFile("resources/font/preview.txt");
     }
 
     private FontInfo fontInfo;
     private FontInfo lastFontInfo;
 
-    private final JList familySelector;
+    private final FontSelectList familySelector;
     private final JComboBox sizeSelector;
     private final JCheckBox isBoldCheck;
     private final JCheckBox isItalicCheck;
@@ -76,7 +68,7 @@ public class FontChooser extends JPanel
     private final JCheckBox useFractionalCheck;
     private final JLabel maxBounds;
     private final JTextField decodeName;
-    private final FontPreview preview;
+    private final FontPreviewer preview;
     private final JButton resetDefault;
 
     private boolean maskListener = false;
@@ -102,28 +94,13 @@ public class FontChooser extends JPanel
         this.fontInfo = fontInfo;
         this.lastFontInfo = fontInfo;
 
-        Jindolf.logger().info(
-                  "デフォルトの発言表示フォントに"
-                + this.fontInfo.getFont()
-                + "が選択されました" );
-        Jindolf.logger().info(
-                  "発言表示のアンチエイリアス指定に"
-                + this.fontInfo.getFontRenderContext().isAntiAliased()
-                + "が指定されました" );
-        Jindolf.logger().info(
-                  "発言表示のFractional指定に"
-                + this.fontInfo.getFontRenderContext().usesFractionalMetrics()
-                + "が指定されました" );
-
-        this.familySelector = new JList(FontUtils.createFontSet().toArray());
-        this.familySelector.setVisibleRowCount(-1);
-        this.familySelector
-            .setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+        logging(this.fontInfo);
+
+        this.familySelector = new FontSelectList();
 
         this.sizeSelector = new JComboBox();
         this.sizeSelector.setEditable(true);
-        this.sizeSelector.setActionCommand(ActionManager.CMD_FONTSIZESEL);
-        for(Integer size : POINT_SIZES){
+        for(int size : POINT_SIZES){
             this.sizeSelector.addItem(size);
         }
 
@@ -140,14 +117,12 @@ public class FontChooser extends JPanel
         this.decodeName.setComponentPopupMenu(new TextPopup());
         Monodizer.monodize(this.decodeName);
 
-        this.preview = new FontPreview(PREVIEW_CONTENT, this.fontInfo);
+        this.preview = new FontPreviewer(PREVIEW_CONTENT, this.fontInfo);
 
         this.resetDefault = new JButton("出荷時に戻す");
-        this.resetDefault.addActionListener(this);
 
         design(this);
         updateControlls();
-        updatePreview();
 
         this.familySelector.addListSelectionListener(this);
         this.sizeSelector  .addActionListener(this);
@@ -157,6 +132,31 @@ public class FontChooser extends JPanel
         this.useTextAntiAliaseCheck.addItemListener(this);
         this.useFractionalCheck    .addItemListener(this);
 
+        this.resetDefault.addActionListener(this);
+
+        return;
+    }
+
+    /**
+     * フォント情報に関するログ出力。
+     * @param info フォント情報
+     */
+    private static void logging(FontInfo info){
+        String form;
+        String logMsg;
+
+        form = "発言表示フォントに{0}が選択されました。";
+        logMsg = MessageFormat.format(form, info.getFont());
+        LOGGER.info(logMsg);
+
+        form = "発言表示のアンチエイリアス指定に{0}が指定されました。";
+        logMsg = MessageFormat.format(form, info.isAntiAliased());
+        LOGGER.info(logMsg);
+
+        form = "発言表示のFractional指定に{0}が指定されました。";
+        logMsg = MessageFormat.format(form, info.usesFractionalMetrics());
+        LOGGER.info(logMsg);
+
         return;
     }
 
@@ -166,50 +166,39 @@ public class FontChooser extends JPanel
      */
     private void design(Container content){
         GridBagLayout layout = new GridBagLayout();
-        GridBagConstraints constraints = new GridBagConstraints();
-
         content.setLayout(layout);
 
-        Border border;
-        JPanel panel;
-
-        JComponent fontPref = createFontPrefPanel();
-
+        GridBagConstraints constraints = new GridBagConstraints();
         constraints.insets = new Insets(5, 5, 5, 5);
 
-        constraints.weightx = 1.0;
-        constraints.weighty = 0.0;
+        constraints.weightx   = 1.0;
+        constraints.weighty   = 0.0;
         constraints.gridwidth = GridBagConstraints.REMAINDER;
-        constraints.fill = GridBagConstraints.BOTH;
-        content.add(fontPref, constraints);
+        constraints.fill      = GridBagConstraints.BOTH;
+        content.add(createFontPrefPanel(), constraints);
 
-        constraints.weightx = 1.0;
-        constraints.weighty = 1.0;
+        constraints.weightx   = 1.0;
+        constraints.weighty   = 1.0;
         constraints.gridwidth = GridBagConstraints.REMAINDER;
-        constraints.fill = GridBagConstraints.BOTH;
-        border = BorderFactory.createTitledBorder("プレビュー");
-        panel = new JPanel();
-        panel.add(this.preview);
-        panel.setBorder(border);
+        constraints.fill      = GridBagConstraints.BOTH;
         content.add(createPreviewPanel(), constraints);
 
-        constraints.weightx = 1.0;
-        constraints.weighty = 0.0;
+        constraints.weightx   = 1.0;
+        constraints.weighty   = 0.0;
         constraints.gridwidth = GridBagConstraints.REMAINDER;
-        constraints.fill = GridBagConstraints.HORIZONTAL;
+        constraints.fill      = GridBagConstraints.HORIZONTAL;
         content.add(createFontDecodePanel(), constraints);
 
-        constraints.insets = new Insets(5, 5, 5, 5);
-        constraints.weightx = 1.0;
-        constraints.weighty = 0.0;
+        constraints.weightx   = 1.0;
+        constraints.weighty   = 0.0;
         constraints.gridwidth = 1;
-        constraints.fill = GridBagConstraints.HORIZONTAL;
+        constraints.fill      = GridBagConstraints.HORIZONTAL;
         content.add(this.maxBounds, constraints);
 
-        constraints.weightx = 0.0;
-        constraints.weighty = 0.0;
+        constraints.weightx   = 0.0;
+        constraints.weighty   = 0.0;
         constraints.gridwidth = GridBagConstraints.REMAINDER;
-        constraints.fill = GridBagConstraints.HORIZONTAL;
+        constraints.fill      = GridBagConstraints.HORIZONTAL;
         content.add(this.resetDefault, constraints);
 
         return;
@@ -219,47 +208,50 @@ public class FontChooser extends JPanel
      * フォント設定画面を生成する。
      * @return フォント設定画面
      */
-    private JComponent createFontPrefPanel(){
+    private JPanel createFontPrefPanel(){
         JPanel result = new JPanel();
 
         GridBagLayout layout = new GridBagLayout();
         GridBagConstraints constraints = new GridBagConstraints();
         result.setLayout(layout);
 
-        Border border;
+        JPanel familyBorderPanel = new JPanel();
+        Border familyBorder =
+                BorderFactory.createTitledBorder("フォントファミリ選択");
+        familyBorderPanel.setBorder(familyBorder);
 
-        constraints.insets = new Insets(0, 0, 0, 5);
-        constraints.weightx = 1.0;
-        constraints.weighty = 0.0;
-        constraints.gridheight = GridBagConstraints.REMAINDER;
-        constraints.fill = GridBagConstraints.BOTH;
-        border = BorderFactory.createEmptyBorder(1, 1, 1, 1);
-        this.familySelector.setBorder(border);
+        JPanel sizeBorderPanel = new JPanel();
+        Border sizeBorder =
+                BorderFactory.createTitledBorder("ポイントサイズ指定");
+        sizeBorderPanel.setBorder(sizeBorder);
+
+        Border scrollBorder = BorderFactory.createEmptyBorder(1, 1, 1, 1);
+        this.familySelector.setBorder(scrollBorder);
         JScrollPane familyScroller = new JScrollPane(this.familySelector);
-        border = BorderFactory.createTitledBorder("フォントファミリ選択");
-        JPanel familyPanel = new JPanel();
-        familyPanel.setLayout(new BorderLayout());
-        familyPanel.add(familyScroller, BorderLayout.CENTER);
-        familyPanel.setBorder(border);
-        result.add(familyPanel, constraints);
-
-        constraints.insets = new Insets(0, 0, 0, 0);
-        constraints.weightx = 0.0;
-        constraints.gridheight = 1;
-        constraints.fill = GridBagConstraints.HORIZONTAL;
-        constraints.anchor = GridBagConstraints.WEST;
 
-        border = BorderFactory.createTitledBorder("ポイントサイズ指定");
-        JPanel panel = new JPanel();
-        panel.add(this.sizeSelector);
-        panel.setBorder(border);
-        result.add(panel, constraints);
+        familyBorderPanel.setLayout(new BorderLayout());
+        familyBorderPanel.add(familyScroller);
+        constraints.insets     = new Insets(0, 0, 0, 5);
+        constraints.weightx    = 1.0;
+        constraints.weighty    = 0.0;
+        constraints.gridheight = GridBagConstraints.REMAINDER;
+        constraints.fill       = GridBagConstraints.BOTH;
+        result.add(familyBorderPanel, constraints);
+
+        sizeBorderPanel.setLayout(new BorderLayout());
+        sizeBorderPanel.add(this.sizeSelector);
+        constraints.insets     = new Insets(0, 0, 0, 0);
+        constraints.weightx    = 0.0;
+        constraints.gridheight = 1;
+        constraints.fill       = GridBagConstraints.HORIZONTAL;
+        constraints.anchor     = GridBagConstraints.WEST;
+        result.add(sizeBorderPanel, constraints);
 
         constraints.anchor = GridBagConstraints.NORTHWEST;
-        result.add(this.isBoldCheck, constraints);
-        result.add(this.isItalicCheck, constraints);
+        result.add(this.isBoldCheck,            constraints);
+        result.add(this.isItalicCheck,          constraints);
         result.add(this.useTextAntiAliaseCheck, constraints);
-        result.add(this.useFractionalCheck, constraints);
+        result.add(this.useFractionalCheck,     constraints);
 
         return result;
     }
@@ -268,17 +260,17 @@ public class FontChooser extends JPanel
      * プレビュー画面を生成する。
      * @return プレビュー画面
      */
-    private JComponent createPreviewPanel(){
+    private JPanel createPreviewPanel(){
         JPanel result = new JPanel();
+        Border border =
+                BorderFactory.createTitledBorder("フォントプレビュー");
+        result.setBorder(border);
 
         JScrollPane scroller = new JScrollPane(this.preview);
-        scroller.getVerticalScrollBar().setUnitIncrement(8);
+        scroller.getVerticalScrollBar().setUnitIncrement(UNIT_INC);
 
-        Border border;
-        border = BorderFactory.createTitledBorder("プレビュー");
-        result.setBorder(border);
         result.setLayout(new BorderLayout());
-        result.add(scroller, BorderLayout.CENTER);
+        result.add(scroller);
 
         return result;
     }
@@ -287,22 +279,25 @@ public class FontChooser extends JPanel
      * フォントデコード名表示パネルを生成する。
      * @return フォントデコード名表示パネル
      */
-    private JComponent createFontDecodePanel(){
+    private JPanel createFontDecodePanel(){
         JPanel result = new JPanel();
 
         GridBagLayout layout = new GridBagLayout();
-        GridBagConstraints constraints = new GridBagConstraints();
         result.setLayout(layout);
 
-        constraints.weightx = 0.0;
-        constraints.weighty = 0.0;
+        GridBagConstraints constraints = new GridBagConstraints();
+
+        JLabel label = new JLabel("Font.deode() 識別名:");
+
+        constraints.weightx   = 0.0;
+        constraints.weighty   = 0.0;
         constraints.gridwidth = 1;
-        constraints.fill = GridBagConstraints.NONE;
-        result.add(new JLabel("Font.deode() 識別名:"), constraints);
+        constraints.fill      = GridBagConstraints.NONE;
+        result.add(label, constraints);
 
-        constraints.weightx = 1.0;
+        constraints.weightx   = 1.0;
         constraints.gridwidth = GridBagConstraints.REMAINDER;
-        constraints.fill = GridBagConstraints.HORIZONTAL;
+        constraints.fill      = GridBagConstraints.HORIZONTAL;
         result.add(this.decodeName, constraints);
 
         return result;
@@ -330,51 +325,28 @@ public class FontChooser extends JPanel
         this.fontInfo = newInfo;
 
         updateControlls();
-        updatePreview();
 
         return;
     }
 
     /**
-     * 選択されたフォントを返す。
-     * @return フォント
-     */
-    private Font getSelectedFont(){
-        return this.fontInfo.getFont();
-    }
-
-    /**
-     * 設定されたフォント描画設定を返す。
-     * @return 描画設定
-     */
-    protected FontRenderContext getFontRenderContext(){
-        return this.fontInfo.getFontRenderContext();
-    }
-
-    /**
-     * フォント設定に合わせてプレビュー画面を更新する。
-     */
-    private void updatePreview(){
-        this.preview.setFontInfo(this.fontInfo);
-        return;
-    }
-
-    /**
      * フォント設定に合わせてGUIを更新する。
+     * <p>イベント発火は抑止される。
      */
     private void updateControlls(){
         this.maskListener = true;
 
-        Font currentFont = getSelectedFont();
-        FontRenderContext currentContext = getFontRenderContext();
+        Font currentFont = getFontInfo().getFont();
 
+        // フォント名リスト
         String defaultFamily = currentFont.getFamily();
-        this.familySelector.setSelectedValue(defaultFamily, true);
+        this.familySelector.setSelectedFamily(defaultFamily);
 
+        // サイズ指定コンボボックス
         Integer selectedInteger = Integer.valueOf(currentFont.getSize());
         this.sizeSelector.setSelectedItem(selectedInteger);
         int sizeItems = this.sizeSelector.getItemCount();
-        for(int index = 0; index <= sizeItems - 1; index++){
+        for(int index = 0; index < sizeItems; index++){
             Object sizeItem = this.sizeSelector.getItemAt(index);
             if(sizeItem.equals(selectedInteger)){
                 this.sizeSelector.setSelectedIndex(index);
@@ -382,26 +354,31 @@ public class FontChooser extends JPanel
             }
         }
 
+        // チェックボックス群
         this.isBoldCheck  .setSelected(currentFont.isBold());
         this.isItalicCheck.setSelected(currentFont.isItalic());
-
         this.useTextAntiAliaseCheck
-            .setSelected(currentContext.isAntiAliased());
+            .setSelected(this.fontInfo.isAntiAliased());
         this.useFractionalCheck
-            .setSelected(currentContext.usesFractionalMetrics());
+            .setSelected(this.fontInfo.usesFractionalMetrics());
 
+        // デコード名
         this.decodeName.setText(FontUtils.getFontDecodeName(currentFont));
         this.decodeName.setCaretPosition(0);
 
-        Rectangle2D r2d = currentFont.getMaxCharBounds(currentContext);
-        Rectangle rect = r2d.getBounds();
-        String boundInfo =  "最大文字寸法 : "
-                          + rect.width
-                          + " pixel幅 × "
-                          + rect.height
-                          + " pixel高";
+        // 寸法
+        String form = "最大文字寸法\u0020:\u0020"
+                    + "{0}\u0020pixel幅"
+                    + "\u0020×\u0020"
+                    + "{1}\u0020pixel高";
+        Rectangle rect = this.fontInfo.getMaxCharBounds();
+        String boundInfo =
+                MessageFormat.format(form, rect.width, rect.height);
         this.maxBounds.setText(boundInfo);
 
+        // プレビュー
+        this.preview.setFontInfo(this.fontInfo);
+
         this.maskListener = false;
 
         return;
@@ -409,15 +386,12 @@ public class FontChooser extends JPanel
 
     /**
      * {@inheritDoc}
-     * ダイアログの表示・非表示。
-     * ダイアログが閉じられるまで制御を返さない。
      * @param isVisible trueなら表示 {@inheritDoc}
      */
     @Override
     public void setVisible(boolean isVisible){
         if(isVisible){
             updateControlls();
-            updatePreview();
         }
         this.lastFontInfo = this.fontInfo;
 
@@ -428,47 +402,52 @@ public class FontChooser extends JPanel
 
     /**
      * {@inheritDoc}
-     * ã\83\81ã\82§ã\83\83ã\82¯ã\83\9cã\83\83ã\82¯ã\82¹操作のリスナ。
+     * ã\83\95ã\82©ã\83³ã\83\88ã\83\95ã\82¡ã\83\9fã\83ªã\83ªã\82¹ã\83\88é\81¸æ\8a\9e操作のリスナ。
      * @param event 操作イベント {@inheritDoc}
      */
     @Override
-    public void itemStateChanged(ItemEvent event){
+    public void valueChanged(ListSelectionEvent event){
         if(this.maskListener) return;
 
-        Object source = event.getSource();
+        if(event.getSource() != this.familySelector) return;
+        if(event.getValueIsAdjusting()) return;
 
-        if(   source != this.isBoldCheck
-           && source != this.isItalicCheck
-           && source != this.useTextAntiAliaseCheck
-           && source != this.useFractionalCheck     ){
-            return;
-        }
+        String familyName = this.familySelector.getSelectedFamily();
+        if(familyName == null) return;
 
-        int style = 0 | Font.PLAIN;
-        if(this.isBoldCheck.isSelected()){
-            style = style | Font.BOLD;
-        }
-        if(this.isItalicCheck.isSelected()){
-            style = style | Font.ITALIC;
-        }
-        Font newFont = getSelectedFont();
-        if(newFont.getStyle() != style){
-            newFont = newFont.deriveFont(style);
-        }
+        Font currentFont = getFontInfo().getFont();
+        int style = currentFont.getStyle();
+        int size  = currentFont.getSize();
 
-        AffineTransform tx = getFontRenderContext().getTransform();
-        boolean isAntiAliases = this.useTextAntiAliaseCheck.isSelected();
-        boolean useFractional = this.useFractionalCheck    .isSelected();
-        FontRenderContext newContext =
-                new FontRenderContext(tx, isAntiAliases, useFractional);
+        Font newFont = new Font(familyName, style, size);
+        FontInfo newInfo = this.fontInfo.deriveFont(newFont);
 
-        FontInfo newInfo = new FontInfo(newFont, newContext);
         setFontInfo(newInfo);
 
         return;
     }
 
     /**
+     * {@inheritDoc}
+     * ボタン操作及びフォントサイズ指定コンボボックス操作のリスナ。
+     * @param event 操作イベント {@inheritDoc}
+     */
+    @Override
+    public void actionPerformed(ActionEvent event){
+        if(this.maskListener) return;
+
+        Object source = event.getSource();
+
+        if(source == this.sizeSelector){
+            actionFontSizeSelected();
+        }else if(source == this.resetDefault){
+            setFontInfo(FontInfo.DEFAULT_FONTINFO);
+        }
+
+        return;
+    }
+
+    /**
      * フォントサイズ変更処理。
      */
     private void actionFontSizeSelected(){
@@ -482,159 +461,63 @@ public class FontChooser extends JPanel
             try{
                 selectedInteger = Integer.valueOf(selected.toString());
             }catch(NumberFormatException e){
-                selectedInteger = Integer.valueOf(
-                        this.lastFontInfo.getFont().getSize()
-                        );
+                selectedInteger =  this.lastFontInfo.getFont().getSize();
             }
         }
 
-        if(selectedInteger.intValue() <= 0){
-            selectedInteger =
-                    Integer.valueOf(this.lastFontInfo.getFont().getSize());
+        if(selectedInteger <= 0){
+            selectedInteger = this.lastFontInfo.getFont().getSize();
         }
 
         float fontSize = selectedInteger.floatValue();
-        Font newFont = getSelectedFont().deriveFont(fontSize);
-        FontInfo newInfo = this.fontInfo.deriveFont(newFont);
-        setFontInfo(newInfo);
+        Font newFont = getFontInfo().getFont().deriveFont(fontSize);
+        FontInfo newInfo = getFontInfo().deriveFont(newFont);
 
-        int sizeItems = this.sizeSelector.getItemCount();
-        for(int index = 0; index <= sizeItems - 1; index++){
-            Object sizeItem = this.sizeSelector.getItemAt(index);
-            if(sizeItem.equals(selectedInteger)){
-                this.sizeSelector.setSelectedIndex(index);
-                break;
-            }
-        }
-
-        updateControlls();
-        updatePreview();
+        setFontInfo(newInfo);
 
         return;
     }
 
     /**
      * {@inheritDoc}
-     * ã\83\9cã\82¿ã\83³æ\93\8dä½\9cå\8f\8aã\81³ã\83\95ã\82©ã\83³ã\83\88ã\82µã\82¤ã\82ºæ\8c\87å®\9aã\82³ã\83³ã\83\9cボックス操作のリスナ。
+     * ã\83\81ã\82§ã\83\83ã\82¯ボックス操作のリスナ。
      * @param event 操作イベント {@inheritDoc}
      */
     @Override
-    public void actionPerformed(ActionEvent event){
+    public void itemStateChanged(ItemEvent event){
         if(this.maskListener) return;
 
-        String cmd = event.getActionCommand();
-        if(cmd.equals(ActionManager.CMD_FONTSIZESEL)){
-            actionFontSizeSelected();
-        }
-
         Object source = event.getSource();
-        if(source == this.resetDefault){
-            setFontInfo(FontInfo.DEFAULT_FONTINFO);
-        }
-
-        return;
-    }
-
-    /**
-     * {@inheritDoc}
-     * フォントファミリリスト選択操作のリスナ。
-     * @param event 操作イベント {@inheritDoc}
-     */
-    @Override
-    public void valueChanged(ListSelectionEvent event){
-        if(this.maskListener) return;
-
-        if(event.getSource() != this.familySelector) return;
-        if(event.getValueIsAdjusting()) return;
-
-        Object selected = this.familySelector.getSelectedValue();
-        if(selected == null) return;
-
-        String familyName = selected.toString();
-        Font currentFont = getSelectedFont();
-        int style = currentFont.getStyle();
-        int size  = currentFont.getSize();
-
-        Font newFont = new Font(familyName, style, size);
-        FontInfo newInfo = this.fontInfo.deriveFont(newFont);
-
-        setFontInfo(newInfo);
-
-        return;
-    }
-
-    /**
-     * フォントプレビュー画面用コンポーネント。
-     */
-    private static class FontPreview extends JComponent{
-
-        private static final int MARGIN = 5;
-
-        private final GlyphDraw draw;
-
-        private FontInfo fontInfo;
-
-        /**
-         * コンストラクタ。
-         * @param source 文字列
-         * @param fontInfo フォント設定
-         */
-        public FontPreview(CharSequence source,
-                             FontInfo fontInfo ){
-            super();
-
-            this.fontInfo = fontInfo;
-            this.draw = new GlyphDraw(source, this.fontInfo);
-            this.draw.setFontInfo(this.fontInfo);
-
-            this.draw.setPos(MARGIN, MARGIN);
-
-            this.draw.setColor(Color.BLACK);
-            setBackground(Color.WHITE);
-
-            updateBounds();
 
+        if(   source != this.isBoldCheck
+           && source != this.isItalicCheck
+           && source != this.useTextAntiAliaseCheck
+           && source != this.useFractionalCheck     ){
             return;
         }
 
-        /**
-         * サイズ更新。
-         */
-        private void updateBounds(){
-            Rectangle bounds = this.draw.setWidth(Integer.MAX_VALUE);
-            Dimension dimension = new Dimension(bounds.width  + MARGIN * 2,
-                                                bounds.height + MARGIN * 2 );
-            setPreferredSize(dimension);
-            revalidate();
-            repaint();
-            return;
-        }
+        FontInfo newInfo = getFontInfo();
 
-        /**
-         * フォント設定の変更。
-         * @param newFontInfo フォント設定
-         */
-        public void setFontInfo(FontInfo newFontInfo){
-            this.fontInfo = newFontInfo;
-            this.draw.setFontInfo(this.fontInfo);
+        int style = 0 | Font.PLAIN;
+        if(this.isBoldCheck.isSelected()){
+            style = style | Font.BOLD;
+        }
+        if(this.isItalicCheck.isSelected()){
+            style = style | Font.ITALIC;
+        }
+        Font newFont = newInfo.getFont();
+        if(newFont.getStyle() != style){
+            newFont = newFont.deriveFont(style);
+            newInfo = newInfo.deriveFont(newFont);
+        }
 
-            updateBounds();
+        boolean isAntiAliases = this.useTextAntiAliaseCheck.isSelected();
+        boolean useFractional = this.useFractionalCheck    .isSelected();
+        newInfo = newInfo.deriveRenderContext(isAntiAliases, useFractional);
 
-            return;
-        }
+        setFontInfo(newInfo);
 
-        /**
-         * {@inheritDoc}
-         * 文字列の描画。
-         * @param g {@inheritDoc}
-         */
-        @Override
-        public void paintComponent(Graphics g){
-            super.paintComponent(g);
-            Graphics2D g2d = (Graphics2D) g;
-            this.draw.paint(g2d);
-            return;
-        }
+        return;
     }
 
 }
@@ -5,12 +5,14 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.glyph;
 
 import java.awt.Font;
+import java.awt.Rectangle;
 import java.awt.font.FontRenderContext;
 import java.awt.font.GlyphVector;
 import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
 import java.text.CharacterIterator;
 import jp.sourceforge.jovsonz.JsBoolean;
 import jp.sourceforge.jovsonz.JsNumber;
@@ -27,16 +29,16 @@ public class FontInfo{
     /** デフォルトのフォント設定。 */
     public static final FontInfo DEFAULT_FONTINFO = new FontInfo();
 
-    private static final String HASH_FAMILY = "family";
-    private static final String HASH_SIZE = "size";
-    private static final String HASH_ISBOLD = "isBold";
-    private static final String HASH_ISITALIC = "isItalic";
-    private static final String HASH_USEAA = "useAntiAlias";
+    private static final String HASH_FAMILY     = "family";
+    private static final String HASH_SIZE       = "size";
+    private static final String HASH_ISBOLD     = "isBold";
+    private static final String HASH_ISITALIC   = "isItalic";
+    private static final String HASH_USEAA      = "useAntiAlias";
     private static final String HASH_FRACTIONAL = "useFractional";
 
 
-    private Font font;
-    private FontRenderContext context;
+    private final Font font;
+    private final FontRenderContext context;
 
 
     /**
@@ -78,19 +80,26 @@ public class FontInfo{
 
     /**
      * フォントに応じた最適な描画設定を生成する。
+     * <p>ビットマップフォントと推測されるときは
+     * アンチエイリアスやサブピクセル補完を無効にする。
      * @param font フォント
      * @return 描画設定
+     * @see FontUtils#guessBitmapFont(Font)
      */
     public static FontRenderContext createBestContext(Font font){
-        FontRenderContext result;
-
-        AffineTransform identity = ImtblAffineTx.IDENTITY;
+        boolean isAntiAliased         = true;
+        boolean usesFractionalMetrics = true;
         if(FontUtils.guessBitmapFont(font)){
-            result = new FontRenderContext(identity, false, false);
-        }else{
-            result = new FontRenderContext(identity, true, true);
+            isAntiAliased         = false;
+            usesFractionalMetrics = false;
         }
 
+        AffineTransform identity = ImtblAffineTx.IDENTITY;
+        FontRenderContext result =
+                new FontRenderContext(identity,
+                                      isAntiAliased,
+                                      usesFractionalMetrics);
+
         return result;
     }
 
@@ -100,92 +109,124 @@ public class FontInfo{
      * @return JSON Object
      */
     public static JsObject buildJson(FontInfo fontInfo){
-        Font font = fontInfo.getFont();
+        Font font             = fontInfo.getFont();
         FontRenderContext frc = fontInfo.getFontRenderContext();
-        JsPair type = new JsPair(HASH_FAMILY,
-                                 FontUtils.getRootFamilyName(font) );
-        JsPair size = new JsPair(HASH_SIZE, font.getSize());
-        JsPair bold = new JsPair(HASH_ISBOLD, font.isBold());
+
+        JsPair family = new JsPair(HASH_FAMILY,
+                                   FontUtils.getRootFamilyName(font) );
+        JsPair size   = new JsPair(HASH_SIZE, font.getSize());
+        JsPair bold   = new JsPair(HASH_ISBOLD, font.isBold());
         JsPair italic = new JsPair(HASH_ISITALIC, font.isItalic());
-        JsPair host = new JsPair(HASH_USEAA, frc.isAntiAliased());
-        JsPair port =
-                new JsPair(HASH_FRACTIONAL, frc.usesFractionalMetrics());
+        JsPair aa     = new JsPair(HASH_USEAA, frc.isAntiAliased());
+        JsPair frac   = new JsPair(HASH_FRACTIONAL,
+                                   frc.usesFractionalMetrics() );
 
         JsObject result = new JsObject();
-        result.putPair(type);
+        result.putPair(family);
         result.putPair(size);
         result.putPair(bold);
         result.putPair(italic);
-        result.putPair(host);
-        result.putPair(port);
+        result.putPair(aa);
+        result.putPair(frac);
 
         return result;
     }
 
     /**
-     * JSONã\81\8bã\82\89ã\81®ã\83\95ã\82©ã\83³ã\83\88設å®\9a復元。
+     * JSONã\81\8bã\82\89ã\83\95ã\82©ã\83³ã\83\88ã\82\92復元。
      * @param obj JSON Object
-     * @return フォント設定
+     * @return フォント
      */
-    public static FontInfo decodeJson(JsObject obj){
+    private static Font decodeJsonFont(JsObject obj){
         JsValue value;
 
-        Font newFont = FontUtils.createDefaultSpeechFont();
-        FontRenderContext newFrc = createBestContext(newFont);
-        int style = newFont.getStyle();
-
+        Font font = null;
         value = obj.getValue(HASH_FAMILY);
         if(value instanceof JsString){
             JsString string = (JsString) value;
             Font decoded = Font.decode(string.toRawString());
             if(decoded != null){
-                newFont = decoded;
+                font = decoded;
             }
         }
-
-        int size = newFont.getSize();
-        value = obj.getValue(HASH_SIZE);
-        if(value instanceof JsNumber){
-            JsNumber number = (JsNumber) value;
-            size = number.intValue();
+        if(font == null){
+            font = FontUtils.createDefaultSpeechFont();
         }
 
-        boolean isBold = newFont.isBold();
+        boolean isBold   = false;
+        boolean isItalic = false;
+
         value = obj.getValue(HASH_ISBOLD);
         if(value instanceof JsBoolean){
             JsBoolean bool = (JsBoolean) value;
             isBold = bool.booleanValue();
         }
-        if(isBold) style |= Font.BOLD;
 
-        boolean isItalic = newFont.isItalic();
         value = obj.getValue(HASH_ISITALIC);
         if(value instanceof JsBoolean){
             JsBoolean bool = (JsBoolean) value;
             isItalic = bool.booleanValue();
         }
+
+        int style = Font.PLAIN;
+        if(isBold)   style |= Font.BOLD;
         if(isItalic) style |= Font.ITALIC;
 
-        boolean isAntiAlias = newFrc.isAntiAliased();
+        int size = FontUtils.DEF_SIZE;
+        value = obj.getValue(HASH_SIZE);
+        if(value instanceof JsNumber){
+            JsNumber number = (JsNumber) value;
+            size = number.intValue();
+        }
+
+        Font derivedFont = font.deriveFont(style, (float)size);
+
+        return derivedFont;
+    }
+
+    /**
+     * JSONからフォント描画設定を復元。
+     * @param obj JSON Object
+     * @param font デフォルトフォント
+     * @return フォント描画設定
+     */
+    private static FontRenderContext decodeJsonFrc(JsObject obj,
+                                                   Font font ){
+        FontRenderContext defFrc = createBestContext(font);
+        boolean isAntiAlias   = defFrc.isAntiAliased();
+        boolean useFractional = defFrc.usesFractionalMetrics();
+
+        JsValue value;
+
         value = obj.getValue(HASH_USEAA);
         if(value instanceof JsBoolean){
             JsBoolean bool = (JsBoolean) value;
             isAntiAlias = bool.booleanValue();
         }
 
-        boolean useFractional = newFrc.usesFractionalMetrics();
         value = obj.getValue(HASH_FRACTIONAL);
         if(value instanceof JsBoolean){
             JsBoolean bool = (JsBoolean) value;
             useFractional = bool.booleanValue();
         }
 
-        newFont = newFont.deriveFont(style, (float)size);
+        FontRenderContext newFrc =
+                new FontRenderContext(ImtblAffineTx.IDENTITY,
+                                      isAntiAlias, useFractional );
 
-        newFrc = new FontRenderContext(ImtblAffineTx.IDENTITY,
-                                       isAntiAlias, useFractional);
+        return newFrc;
+    }
 
-        FontInfo result = new FontInfo(newFont, newFrc);
+    /**
+     * JSONからのフォント設定復元。
+     * @param obj JSON Object
+     * @return フォント設定
+     */
+    public static FontInfo decodeJson(JsObject obj){
+        Font font = decodeJsonFont(obj);
+        FontRenderContext frc = decodeJsonFrc(obj, font);
+
+        FontInfo result = new FontInfo(font, frc);
 
         return result;
     }
@@ -207,6 +248,35 @@ public class FontInfo{
     }
 
     /**
+     * アンチエイリアス機能を使うか判定する。
+     * @return アンチエイリアス機能を使うならtrue
+     */
+    public boolean isAntiAliased(){
+        boolean result = this.context.isAntiAliased();
+        return result;
+    }
+
+    /**
+     * サブピクセル精度を使うか判定する。
+     * @return サブピクセル精度を使うならtrue
+     */
+    public boolean usesFractionalMetrics(){
+        boolean result = this.context.usesFractionalMetrics();
+        return result;
+    }
+
+    /**
+     * フォントの最大寸法を返す。
+     * @return 最大寸法
+     * @see java.awt.Font#getMaxCharBounds(FontRenderContext)
+     */
+    public Rectangle getMaxCharBounds(){
+        Rectangle2D r2d = this.font.getMaxCharBounds(this.context);
+        Rectangle rect = r2d.getBounds();
+        return rect;
+    }
+
+    /**
      * フォントのみ異なる設定を派生させる。
      * @param newFont 新フォント
      * @return 新設定
@@ -225,6 +295,20 @@ public class FontInfo{
     }
 
     /**
+     * 描画属性のみ異なる設定を派生させる。
+     * @param isAntiAliases アンチエイリアス設定
+     * @param useFractional サブピクセル精度設定
+     * @return 新設定
+     */
+    public FontInfo deriveRenderContext(boolean isAntiAliases,
+                                        boolean useFractional){
+        AffineTransform tx = this.context.getTransform();
+        FontRenderContext newContext =
+                new FontRenderContext(tx, isAntiAliases, useFractional);
+        return deriveRenderContext(newContext);
+    }
+
+    /**
      * 文字列からグリフ集合を生成する。
      * @param iterator 文字列
      * @return グリフ集合
diff --git a/src/main/java/jp/sfjp/jindolf/glyph/FontListModel.java b/src/main/java/jp/sfjp/jindolf/glyph/FontListModel.java
new file mode 100644 (file)
index 0000000..f7c27f3
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * font list model
+ *
+ * License : The MIT License
+ * Copyright(c) 2012 olyutorskii
+ */
+
+package jp.sfjp.jindolf.glyph;
+
+import java.awt.EventQueue;
+import java.util.LinkedList;
+import java.util.List;
+import javax.swing.AbstractListModel;
+
+/**
+ * フォントファミリ名一覧表示用リストのデータモデル。
+ * <p>環境によってはフォントリストを完成させるのに
+ * 数千msかかる場合があるので、その対策として非同期に一覧を読み込む。
+ * <p>実際のリスト作成は裏で走るタスクにより行われ、
+ * リスト完成の暁にはEDTによりリスナに通知される。
+ * 一般的なリストモデルと同様、
+ * 基本的にスレッド間競合の問題はEDTで解決すること。
+ */
+@SuppressWarnings("serial")
+public class FontListModel extends AbstractListModel {
+
+    private final List<String> familyList = new LinkedList<String>();
+
+    private volatile boolean hasDone = false;
+
+    /**
+     * コンストラクタ。
+     * <p>コンストラクタ完了と同時にリスト生成タスクが裏で走り始める。
+     */
+    public FontListModel(){
+        super();
+
+        Runnable task = createFillTask();
+        startTask(task);
+
+        return;
+    }
+
+    /**
+     * フォントリスト埋めタスクを生成する。
+     * @return タスク
+     */
+    private Runnable createFillTask(){
+        Runnable task = new Runnable(){
+            /** {@inheritDoc} */
+            @Override
+            @SuppressWarnings("CallToThreadYield")
+            public void run(){
+                Thread.yield();
+                fillModel();
+            }
+        };
+
+        return task;
+    }
+
+    /**
+     * フォントファミリ名リストを設定する。
+     * @param familyNames フォントファミリ名のリスト
+     * @return リストの要素数
+     */
+    private int fillList(List<String> familyNames){
+        this.familyList.addAll(familyNames);
+        this.hasDone = true;
+        int size = this.familyList.size();
+        return size;
+    }
+
+    /**
+     * フォントリストを収集しモデルに反映させる。
+     */
+    private void fillModel(){
+        final List<String> fontList = FontUtils.createFontList();
+
+        // スレッド間競合を避けるため、ここより先の処理はEDT任せ。
+        EventQueue.invokeLater(new Runnable(){
+            /** {@inheritDoc} */
+            @Override
+            public void run(){
+                int size = fillList(fontList);
+                if(size <= 0) return;
+
+                int begin = 0;
+                int end   = size - 1;
+                fireContentsChanged(this, begin, end);
+
+                return;
+            }
+        });
+
+        return;
+    }
+
+    /**
+     * フォントリスト埋めタスクを起動する。
+     * @param task タスク
+     */
+    private void startTask(Runnable task){
+        Thread thread = new Thread(task);
+        thread.start();
+        return;
+    }
+
+    /**
+     * モデルが完成済みか否か判定する。
+     * @return モデルが完成していればtrue
+     */
+    public boolean hasCompleted(){
+        return this.hasDone;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param index {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public Object getElementAt(int index){
+        Object result = this.familyList.get(index);
+        return result;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public int getSize(){
+        int result = this.familyList.size();
+        return result;
+    }
+
+}
diff --git a/src/main/java/jp/sfjp/jindolf/glyph/FontPreviewer.java b/src/main/java/jp/sfjp/jindolf/glyph/FontPreviewer.java
new file mode 100644 (file)
index 0000000..ed3d8e6
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * font previewer
+ *
+ * License : The MIT License
+ * Copyright(c) 2012 olyutorskii
+ */
+
+package jp.sfjp.jindolf.glyph;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import javax.swing.JComponent;
+
+/**
+ * フォントプレビュー用コンポーネント。
+ * <p>発言表示部と同じビジュアルを再現する必要のため、GlyphDrawで描画する。
+ */
+@SuppressWarnings("serial")
+public class FontPreviewer extends JComponent {
+
+    private static final int MARGIN = 5;
+
+    private final GlyphDraw draw;
+    private FontInfo fontInfo;
+
+
+    /**
+     * コンストラクタ。
+     * @param source 文字列
+     * @param fontInfo フォント設定
+     */
+    public FontPreviewer(CharSequence source,
+                         FontInfo fontInfo ){
+        super();
+
+        this.fontInfo = fontInfo;
+        this.draw = new GlyphDraw(source, this.fontInfo);
+        this.draw.setFontInfo(this.fontInfo);
+
+        this.draw.setPos(MARGIN, MARGIN);
+
+        this.draw.setColor(Color.BLACK);
+        setBackground(Color.WHITE);
+
+        updateBounds();
+
+        return;
+    }
+
+    /**
+     * サイズ更新。
+     */
+    private void updateBounds(){
+        Rectangle bounds = this.draw.setWidth(Integer.MAX_VALUE);
+        Dimension dimension = new Dimension(bounds.width  + MARGIN * 2,
+                                            bounds.height + MARGIN * 2 );
+
+        setPreferredSize(dimension);
+        revalidate();
+        repaint();
+
+        return;
+    }
+
+    /**
+     * フォント設定の変更。
+     * @param newFontInfo フォント設定
+     */
+    public void setFontInfo(FontInfo newFontInfo){
+        this.fontInfo = newFontInfo;
+        this.draw.setFontInfo(this.fontInfo);
+
+        updateBounds();
+
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 文字列の描画。
+     * @param g {@inheritDoc}
+     */
+    @Override
+    public void paintComponent(Graphics g){
+        super.paintComponent(g);
+        Graphics2D g2d = (Graphics2D) g;
+        this.draw.paint(g2d);
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sfjp/jindolf/glyph/FontSelectList.java b/src/main/java/jp/sfjp/jindolf/glyph/FontSelectList.java
new file mode 100644 (file)
index 0000000..d6e8f2a
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * font select list
+ *
+ * License : The MIT License
+ * Copyright(c) 2012 olyutorskii
+ */
+
+package jp.sfjp.jindolf.glyph;
+
+import javax.swing.JList;
+import javax.swing.ListModel;
+import javax.swing.ListSelectionModel;
+import javax.swing.event.ListDataEvent;
+import javax.swing.event.ListDataListener;
+
+/**
+ * フォント選択リスト。
+ * <p>フォント一覧の遅延読み込みに対応。
+ */
+@SuppressWarnings("serial")
+public class FontSelectList extends JList
+        implements ListDataListener {
+
+    private String selectedFamily = null;
+
+
+    /**
+     * コンストラクタ。
+     */
+    public FontSelectList(){
+        super();
+
+        ListModel fontListModel = new FontListModel();
+        setModelImpl(fontListModel);
+        setVisibleRowCount(-1);
+        setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+
+        return;
+    }
+
+    /**
+     * {@link setModel(ListModel)} の下請けメソッド。
+     * 与えられたモデルの更新は監視対象となる。
+     * @param model リストモデル
+     */
+    private void setModelImpl(ListModel model){
+        ListModel oldModel = getModel();
+        if(oldModel != null){
+            oldModel.removeListDataListener(this);
+        }
+
+        model.addListDataListener(this);
+
+        super.setModel(model);
+
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 与えられたモデルの更新は監視対象となる。
+     * @param model {@inheritDoc}
+     */
+    @Override
+    public void setModel(ListModel model){
+        setModelImpl(model);
+        return;
+    }
+
+    /**
+     * 指定したフォントファミリ名が選択された状態にする。
+     * @param family フォントファミリ名
+     */
+    public void setSelectedFamily(String family){
+        this.selectedFamily = family;
+        reSelectFamily();
+        return;
+    }
+
+    /**
+     * 選択されたファミリ名を返す。
+     * @return 選択されたファミリ名。何も選択されていなければnull
+     */
+    public String getSelectedFamily(){
+        Object selected = getSelectedValue();
+        if(selected == null) return null;
+        String result = selected.toString();
+        return result;
+    }
+
+    /**
+     * 過去に指示された選択ファミリを用いて再選択操作を試みる。
+     */
+    private void reSelectFamily(){
+        boolean shouldScroll = true;
+        setSelectedValue(this.selectedFamily, shouldScroll);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * データモデル変更に伴い再選択処理を行う。
+     * @param event {@inheritDoc}
+     */
+    @Override
+    public void contentsChanged(ListDataEvent event){
+        reSelectFamily();
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * データモデル変更に伴い再選択処理を行う。
+     * @param event {@inheritDoc}
+     */
+    @Override
+    public void intervalAdded(ListDataEvent event){
+        reSelectFamily();
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * データモデル変更に伴い再選択処理を行う。
+     * @param event {@inheritDoc}
+     */
+    @Override
+    public void intervalRemoved(ListDataEvent event){
+        reSelectFamily();
+        return;
+    }
+
+}
@@ -5,14 +5,16 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.glyph;
 
 import java.awt.Font;
 import java.awt.GraphicsEnvironment;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
 import java.util.Locale;
-import java.util.SortedSet;
-import java.util.TreeSet;
+import java.util.Set;
 
 /**
  * フォントユーティリティ。
@@ -21,8 +23,13 @@ public final class FontUtils{
 
     /** Font.DIALOG代替品。 */
     public static final String FAMILY_DIALOG = "Dialog";
-    /** Locale.ROOT代替品。 */
-    private static final Locale ROOT = new Locale("", "", "");
+    /** デフォルトのポイントサイズ。 */
+    public static final int DEF_SIZE = 16;
+
+    // Locale.ROOT代替品。@see Locale#ROOT
+    private static final Locale LOCALE_ROOT = new Locale("", "", "");
+
+    private static final int MS_BMP_LIMIT = 24;
 
     private static final String[] INIT_FAMILY_NAMES = {
         "Hiragino Kaku Gothic Pro",  // for MacOS X
@@ -30,11 +37,15 @@ public final class FontUtils{
         "Osaka",
         "MS PGothic",                // for WinXP
         "MS Gothic",
+        "IPAMonaPGothic",
         // TODO X11用のおすすめは?
     };
 
     /** JIS0208:1990 チェック用。 */
-    private static final String JPCHECK_CODE = "9Aあゑアアヴヰ┼ЖΩ峠凜熙";
+    private static final String JPCHECK_CODE = "あ凜熙峠ゑアアヴヰ┼ЖΩ9A";
+
+    /** 二重引用符。 */
+    private static final char DQ = '"';
 
 
     /**
@@ -47,18 +58,18 @@ public final class FontUtils{
 
     /**
      * システムに存在する有効なファミリ名か判定する。
-     * @param family フォントファミリ名。
+     * @param familyName フォントファミリ名。
      * @return 存在する有効なファミリ名ならtrue
      */
-    public static boolean isValidFamilyName(String family){
+    public static boolean isValidFamilyName(String familyName){
         int style = 0x00 | Font.PLAIN;
         int size = 1;
-        Font dummyFont = new Font(family, style, size);
+        Font dummyFont = new Font(familyName, style, size);
 
-        String dummyFamily      = getRootFamilyName(dummyFont);
-        String dummyLocalFamily = dummyFont.getFamily();
-        if(dummyFamily     .equals(family)) return true;
-        if(dummyLocalFamily.equals(family)) return true;
+        String dummyFamilyName      = getRootFamilyName(dummyFont);
+        String dummyLocalFamilyName = dummyFont.getFamily();
+        if(dummyFamilyName     .equals(familyName)) return true;
+        if(dummyLocalFamilyName.equals(familyName)) return true;
 
         return false;
     }
@@ -78,7 +89,7 @@ public final class FontUtils{
         }
 
         int style = 0x00 | Font.PLAIN;
-        int size = 16;
+        int size = DEF_SIZE;
         Font result = new Font(defaultFamilyName, style, size);
 
         return result;
@@ -86,22 +97,26 @@ public final class FontUtils{
 
     /**
      * ソートされたフォントファミリ一覧表を生成する。
-     * JISX0208:1990相当が表示できないファミリは弾かれる。
-     * 結構実行時間がかかるかも。乱用禁物。
+     * <p>JISX0208:1990相当が表示できないファミリは弾かれる。
+     * <p>結構実行時間がかかるかも(数千ms)。乱用禁物。
      * @return フォント一覧
      */
-    public static SortedSet<String> createFontSet(){
+    public static List<String> createFontList(){
         GraphicsEnvironment ge =
                 GraphicsEnvironment.getLocalGraphicsEnvironment();
+        Font[] allFonts = ge.getAllFonts();
 
-        SortedSet<String> result = new TreeSet<String>();
-        for(Font font : ge.getAllFonts()){
-            if(font.canDisplayUpTo(JPCHECK_CODE) >= 0) continue;
+        Set<String> checked = new HashSet<String>();
+        for(Font font : allFonts){
             String familyName = font.getFamily();
-            result.add(familyName.intern());
+            if(checked.contains(familyName)) continue;
+            if(font.canDisplayUpTo(JPCHECK_CODE) >= 0) continue;
+            checked.add(familyName);
         }
+        List<String> result = new ArrayList<String>(checked);
+        Collections.sort(result);
 
-        return Collections.unmodifiableSortedSet(result);
+        return result;
     }
 
     /**
@@ -113,7 +128,7 @@ public final class FontUtils{
      */
     public static boolean guessBitmapFont(Font font){
         String familyName = getRootFamilyName(font);
-        if(   font.getSize() < 24
+        if(   font.getSize() < MS_BMP_LIMIT
            && familyName.startsWith("MS")
            && (   familyName.contains("Gothic")
                || familyName.contains("Mincho")    ) ){
@@ -124,24 +139,30 @@ public final class FontUtils{
 
     /**
      * Font#decode()用の名前を返す。
+     * 空白が含まれる場合は二重引用符で囲まれる。
      * @param font フォント
      * @return Font#decode()用の名前
+     * @see Font#decode(String)
      */
     public static String getFontDecodeName(Font font){
         StringBuilder result = new StringBuilder();
 
+        String familyName = getRootFamilyName(font);
+
         StringBuilder style = new StringBuilder();
-        if(font.isBold())   style.append("BOLD");
-        if(font.isItalic()) style.append("ITALIC");
+        if(font.isBold())       style.append("BOLD");
+        if(font.isItalic())     style.append("ITALIC");
         if(style.length() <= 0) style.append("PLAIN");
 
-        result.append(getRootFamilyName(font));
-        result.append('-').append(style);
-        result.append('-').append(font.getSize());
+        int fontSize = font.getSize();
+
+        result.append(familyName)
+              .append('-').append(style)
+              .append('-').append(fontSize);
 
         if(   result.indexOf("\u0020") >= 0
            || result.indexOf("\u3000") >= 0 ){
-            result.insert(0, '"').append('"');
+            result.insert(0, DQ).append(DQ);
         }
 
         return result.toString();
@@ -152,9 +173,10 @@ public final class FontUtils{
      * JRE1.5対策
      * @param font フォント
      * @return ファミリ名
+     * @see Font#getFamily(Locale)
      */
     public static String getRootFamilyName(Font font){
-        return font.getFamily(ROOT);
+        return font.getFamily(LOCALE_ROOT);
     }
 
 }
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.glyph;
 
 import java.awt.Color;
 import java.awt.FontMetrics;
@@ -23,6 +23,8 @@ import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import javax.swing.SwingConstants;
+import jp.sfjp.jindolf.data.Anchor;
+import jp.sfjp.jindolf.util.GUIUtils;
 
 /**
  * 複数行の文字列を矩形内に描画する。
@@ -5,7 +5,7 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.glyph;
 
 import java.awt.geom.AffineTransform;
 
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.glyph;
 
 import java.awt.Point;
 import java.io.IOException;
@@ -5,13 +5,15 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.glyph;
 
 import java.awt.Color;
 import java.awt.Graphics2D;
 import java.awt.Point;
 import java.awt.Rectangle;
 import java.io.IOException;
+import jp.sfjp.jindolf.data.DialogPref;
+import jp.sfjp.jindolf.data.SysEvent;
 import jp.sourceforge.jindolf.parser.DecodedContent;
 
 /**
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.glyph;
 
 import java.awt.Color;
 import java.awt.Font;
@@ -20,6 +20,13 @@ import java.text.DateFormat;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.regex.Pattern;
+import jp.sfjp.jindolf.data.Anchor;
+import jp.sfjp.jindolf.data.Avatar;
+import jp.sfjp.jindolf.data.DialogPref;
+import jp.sfjp.jindolf.data.Period;
+import jp.sfjp.jindolf.data.Talk;
+import jp.sfjp.jindolf.data.Village;
+import jp.sfjp.jindolf.util.GUIUtils;
 import jp.sourceforge.jindolf.corelib.TalkType;
 
 /**
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.glyph;
 
 import java.awt.Graphics2D;
 import java.awt.Rectangle;
diff --git a/src/main/java/jp/sfjp/jindolf/glyph/package-info.java b/src/main/java/jp/sfjp/jindolf/glyph/package-info.java
new file mode 100644 (file)
index 0000000..cf68a13
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * パッケージ情報
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 olyutorskii
+ */
+
+/**
+ * 独自の文字列描画処理関連の一連のクラス。
+ */
+
+package jp.sfjp.jindolf.glyph;
+
+/* EOF */
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.log;
 
 import java.awt.Container;
 import java.awt.EventQueue;
@@ -17,11 +17,7 @@ import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.WindowEvent;
 import java.awt.event.WindowListener;
-import java.util.logging.Formatter;
 import java.util.logging.Handler;
-import java.util.logging.Level;
-import java.util.logging.LogRecord;
-import java.util.logging.SimpleFormatter;
 import javax.swing.BorderFactory;
 import javax.swing.JButton;
 import javax.swing.JDialog;
@@ -31,96 +27,24 @@ import javax.swing.JScrollPane;
 import javax.swing.JSeparator;
 import javax.swing.JTextArea;
 import javax.swing.border.Border;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
 import javax.swing.text.BadLocationException;
 import javax.swing.text.Document;
 import javax.swing.text.PlainDocument;
+import jp.sfjp.jindolf.dxchg.TextPopup;
+import jp.sfjp.jindolf.util.GUIUtils;
+import jp.sfjp.jindolf.util.Monodizer;
 
 /**
  * ログ表示パネル。
  */
 @SuppressWarnings("serial")
 public class LogFrame extends JDialog
-        implements WindowListener, ActionListener{
+        implements WindowListener, ActionListener, DocumentListener{
 
-    private static final String FRAMETITLE = "ログ表示 - " + Jindolf.TITLE;
-    private static final int DOCLIMIT = 100 * 1000; // 単位は文字
-    private static final float CHOPRATIO = 0.9f;
-    private static final int CHOPPEDLEN = (int)(DOCLIMIT * CHOPRATIO);
     private static final Document DOC_EMPTY = new PlainDocument();
 
-    static{
-        assert DOCLIMIT > CHOPPEDLEN;
-    }
-
-    /**
-     * ログハンドラ。
-     */
-    private class SwingLogger extends Handler{
-
-        /**
-         * ログハンドラの生成。
-         */
-        public SwingLogger(){
-            super();
-            Formatter formatter = new SimpleFormatter();
-            setFormatter(formatter);
-            return;
-        }
-
-        /**
-         * {@inheritDoc}
-         * @param record {@inheritDoc}
-         */
-        @Override
-        public void publish(LogRecord record){
-            if( ! isLoggable(record) ){
-                return;
-            }
-            Formatter formatter = getFormatter();
-            String message = formatter.format(record);
-            try{
-                LogFrame.this.document
-                        .insertString(LogFrame.this.document.getLength(),
-                                      message,
-                                      null );
-            }catch(BadLocationException e){
-                // IGNORE
-            }
-
-            int docLength = LogFrame.this.document.getLength();
-            if(docLength > DOCLIMIT){
-                int offset = docLength - CHOPPEDLEN;
-                try{
-                    LogFrame.this.document.remove(0, offset);
-                }catch(BadLocationException e){
-                    // IGNORE
-                }
-            }
-
-            showLastPos();
-
-            return;
-        }
-
-        /**
-         * {@inheritDoc}
-         * (何もしない)。
-         */
-        @Override
-        public void flush(){
-            return;
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public void close(){
-            setLevel(Level.OFF);
-            flush();
-            return;
-        }
-    };
 
     private final JTextArea textarea;
     private final Document document = new PlainDocument();
@@ -136,10 +60,11 @@ public class LogFrame extends JDialog
      * @param owner フレームオーナー
      */
     public LogFrame(Frame owner){
-        super(owner, FRAMETITLE, false);
+        super(owner);
+        setModal(false);
 
-        if(Jindolf.hasLoggingPermission()){
-            this.handler = new SwingLogger();
+        if(LogUtils.hasLoggingPermission()){
+            this.handler = new SwingDocHandler(this.document);
         }else{
             this.handler = null;
         }
@@ -164,6 +89,8 @@ public class LogFrame extends JDialog
         this.clearButton.addActionListener(this);
         this.closeButton.addActionListener(this);
 
+        this.document.addDocumentListener(this);
+
         setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
         addWindowListener(this);
 
@@ -354,4 +281,34 @@ public class LogFrame extends JDialog
         return;
     }
 
+    /**
+     * {@inheritDoc}
+     * @param event {@inheritDoc}
+     */
+    @Override
+    public void changedUpdate(DocumentEvent event){
+        showLastPos();
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param event {@inheritDoc}
+     */
+    @Override
+    public void insertUpdate(DocumentEvent event){
+        showLastPos();
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param event {@inheritDoc}
+     */
+    @Override
+    public void removeUpdate(DocumentEvent event){
+        showLastPos();
+        return;
+    }
+
 }
diff --git a/src/main/java/jp/sfjp/jindolf/log/LogUtils.java b/src/main/java/jp/sfjp/jindolf/log/LogUtils.java
new file mode 100644 (file)
index 0000000..2672d54
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * logging common
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 olyutorskii
+ */
+
+package jp.sfjp.jindolf.log;
+
+import java.util.List;
+import java.util.logging.ConsoleHandler;
+import java.util.logging.Handler;
+import java.util.logging.Logger;
+import java.util.logging.LoggingPermission;
+
+/**
+ * ロギングの各種ユーティリティ。
+ */
+public final class LogUtils {
+
+    /** ログ管理用パーミッション。 */
+    public static final LoggingPermission PERM_LOGCTL =
+            new LoggingPermission("control", null);
+
+    /**
+     * 隠しコンストラクタ。
+     */
+    private LogUtils(){
+        super();
+        return;
+    }
+
+    /**
+     * ログ操作のアクセス権があるか否か判定する。
+     * @return アクセス権があればtrue
+     */
+    public static boolean hasLoggingPermission(){
+        SecurityManager manager = System.getSecurityManager();
+        boolean result = hasLoggingPermission(manager);
+        return result;
+    }
+
+    /**
+     * ログ操作のアクセス権があるか否か判定する。
+     * @param manager セキュリティマネージャ
+     * @return アクセス権があればtrue
+     */
+    public static boolean hasLoggingPermission(SecurityManager manager){
+        if(manager == null) return true;
+
+        try{
+            manager.checkPermission(PERM_LOGCTL);
+        }catch(SecurityException e){
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * ルートロガーの初期化を行う。
+     * ルートロガーの既存ハンドラを全解除し、
+     * {@link PileHandler}ハンドラを登録する。
+     * @param useConsoleLog trueなら
+     * {@link java.util.logging.ConsoleHandler}も追加する。
+     */
+    public static void initRootLogger(boolean useConsoleLog){
+        if( ! hasLoggingPermission() ){
+            System.err.println(
+                      "セキュリティ設定により、"
+                    + "ログ設定を変更できませんでした" );
+            return;
+        }
+
+        Logger rootLogger = Logger.getLogger("");
+
+        for(Handler handler : rootLogger.getHandlers()){
+            rootLogger.removeHandler(handler);
+        }
+
+        Handler pileHandler = new PileHandler();
+        rootLogger.addHandler(pileHandler);
+
+        if(useConsoleLog){
+            Handler consoleHandler = new ConsoleHandler();
+            rootLogger.addHandler(consoleHandler);
+        }
+
+        return;
+    }
+
+    /**
+     * ロガーに新ハンドラを追加する。
+     * ロガー中の全{@link PileHandler}型ハンドラに蓄積されていたログは、
+     * 新ハンドラに一気に転送される。
+     * {@link PileHandler}型ハンドラはロガーから削除される。
+     * ログ操作のパーミッションがない場合、何もしない。
+     * @param logger ロガー
+     * @param newHandler 新ハンドラ
+     */
+    public static void switchHandler(Logger logger, Handler newHandler){
+        if( ! hasLoggingPermission() ) return;
+
+        List<PileHandler> pileHandlers = PileHandler.getPileHandlers(logger);
+        PileHandler.removePileHandlers(logger);
+
+        logger.addHandler(newHandler);
+
+        for(PileHandler pileHandler : pileHandlers){
+            pileHandler.delegate(newHandler);
+            pileHandler.close();
+        }
+
+        return;
+    }
+
+}
@@ -5,7 +5,7 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.log;
 
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
@@ -31,6 +31,15 @@ public class LogWrapper{
     }
 
     /**
+     * コンストラクタ。
+     * 新規生成された匿名ロガーを囲う。
+     */
+    public LogWrapper(){
+        this(Logger.getAnonymousLogger());
+        return;
+    }
+
+    /**
      * ラップ対象のjava.util.loggingロガーを取得する。
      * @return ラップ対象のjava.util.loggingロガー
      */
diff --git a/src/main/java/jp/sfjp/jindolf/log/LoggingDispatcher.java b/src/main/java/jp/sfjp/jindolf/log/LoggingDispatcher.java
new file mode 100644 (file)
index 0000000..af32849
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * event dispatcher with logging
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 olyutorskii
+ */
+
+package jp.sfjp.jindolf.log;
+
+import java.awt.AWTEvent;
+import java.awt.EventQueue;
+import java.awt.Toolkit;
+
+/**
+ * 異常系をロギングするイベントディスパッチャ。
+ */
+public class LoggingDispatcher extends EventQueue{
+
+    private static final String FATALMSG =
+            "イベントディスパッチ中に異常が起きました。";
+
+    private static final LogWrapper LOGGER = new LogWrapper();
+
+    /**
+     * コンストラクタ。
+     */
+    public LoggingDispatcher(){
+        super();
+        return;
+    }
+
+    /**
+     * 独自ロガーにエラーや例外を吐く、
+     * カスタム化されたイベントキューに差し替える。
+     */
+    public static void replaceEventQueue(){
+        Toolkit kit = Toolkit.getDefaultToolkit();
+        EventQueue oldQueue = kit.getSystemEventQueue();
+        EventQueue newQueue = new LoggingDispatcher();
+        oldQueue.push(newQueue);
+        return;
+    }
+
+    /**
+     * 異常系をログ出力。
+     * @param e 例外
+     */
+    private void errlog(Throwable e){
+        LOGGER.fatal(FATALMSG, e);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 発生した例外をログ出力する。
+     * @param event {@inheritDoc}
+     */
+    @Override
+    protected void dispatchEvent(AWTEvent event){
+        try{
+            super.dispatchEvent(event);
+        }catch(RuntimeException e){
+            errlog(e);
+            throw e;
+        }catch(Exception e){
+            errlog(e);
+        }catch(Error e){
+            errlog(e);
+            throw e;
+        }
+        // TODO Toolkit#beep()もするべきか
+        // TODO モーダルダイアログを出すべきか
+        // TODO 標準エラー出力抑止オプションを用意すべきか
+        // TODO セキュリティバイパス
+        return;
+    }
+
+}
@@ -5,13 +5,15 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.log;
 
+import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.logging.Handler;
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
+import java.util.logging.Logger;
 
 /**
  * なにもしないロギングハンドラ。
@@ -20,6 +22,8 @@ import java.util.logging.LogRecord;
 public class PileHandler extends Handler{
 
     private final List<LogRecord> logList = new LinkedList<LogRecord>();
+    private final List<LogRecord> unmodList =
+            Collections.unmodifiableList(this.logList);
 
     /**
      * ロギングハンドラを生成する。
@@ -30,16 +34,57 @@ public class PileHandler extends Handler{
     }
 
     /**
+     * ロガーに含まれる{@link PileHandler}型ハンドラのリストを返す。
+     * @param logger ロガー
+     * @return {@link PileHandler}型ハンドラのリスト
+     */
+    public static List<PileHandler> getPileHandlers(Logger logger){
+        List<PileHandler> result = new LinkedList<PileHandler>();
+
+        for(Handler handler : logger.getHandlers()){
+            if( ! (handler instanceof PileHandler) ) continue;
+            PileHandler pileHandler = (PileHandler) handler;
+            result.add(pileHandler);
+        }
+
+        return result;
+    }
+
+    /**
+     * ロガーに含まれる{@link PileHandler}型ハンドラを全て削除する。
+     * @param logger ロガー
+     */
+    public static void removePileHandlers(Logger logger){
+        for(PileHandler pileHandler : getPileHandlers(logger)){
+            logger.removeHandler(pileHandler);
+        }
+        return;
+    }
+
+    /**
+     * 蓄積されたログレコードのリストを返す。
+     * 古いログが先頭に来る。
+     * @return ログレコードのリスト。変更不可。
+     */
+    public List<LogRecord> getRecordList(){
+        return this.unmodList;
+    }
+
+    /**
      * {@inheritDoc}
      * ログを内部に溜め込む。
      * @param record {@inheritDoc}
      */
     @Override
     public void publish(LogRecord record){
+        if(record == null) return;
+
         if( ! isLoggable(record) ){
             return;
         }
+
         this.logList.add(record);
+
         return;
     }
 
@@ -64,6 +109,7 @@ public class PileHandler extends Handler{
 
     /**
      * 他のハンドラへ蓄積したログをまとめて出力する。
+     * 最後に自分自身をクローズし、蓄積されたログを解放する。
      * @param handler 他のハンドラ
      */
     public void delegate(Handler handler){
diff --git a/src/main/java/jp/sfjp/jindolf/log/SwingDocHandler.java b/src/main/java/jp/sfjp/jindolf/log/SwingDocHandler.java
new file mode 100644 (file)
index 0000000..5933331
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * Logging handler for Swing text component
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 olyutorskii
+ */
+
+package jp.sfjp.jindolf.log;
+
+import java.util.logging.Formatter;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.SimpleFormatter;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+
+/**
+ * Swingテキストコンポーネント用データモデル{@link javax.swing.text.Document}
+ * に出力する{@link java.util.logging.Handler}。
+ */
+public class SwingDocHandler extends Handler{
+
+    private static final int DOCLIMIT = 100 * 1000; // 単位は文字
+    private static final float CHOPRATIO = 0.9f;
+    private static final int CHOPPEDLEN = (int)(DOCLIMIT * CHOPRATIO);
+
+    static{
+        assert DOCLIMIT > CHOPPEDLEN;
+    }
+
+
+    private final Document document;
+
+    /**
+     * ログハンドラの生成。
+     * @param document ドキュメントモデル
+     */
+    public SwingDocHandler(Document document){
+        super();
+
+        this.document = document;
+
+        Formatter formatter = new SimpleFormatter();
+        setFormatter(formatter);
+
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param record {@inheritDoc}
+     */
+    @Override
+    public void publish(LogRecord record){
+        if( ! isLoggable(record) ){
+            return;
+        }
+        Formatter formatter = getFormatter();
+        String message = formatter.format(record);
+        try{
+            this.document.insertString(this.document.getLength(),
+                                       message,
+                                       null );
+        }catch(BadLocationException e){
+            assert false;
+        }
+
+        int docLength = this.document.getLength();
+        if(docLength > DOCLIMIT){
+            int offset = docLength - CHOPPEDLEN;
+            try{
+                this.document.remove(0, offset);
+            }catch(BadLocationException e){
+                assert false;
+            }
+        }
+
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * (何もしない)。
+     */
+    @Override
+    public void flush(){
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void close(){
+        setLevel(Level.OFF);
+        flush();
+        return;
+    }
+
+}
diff --git a/src/main/java/jp/sfjp/jindolf/log/package-info.java b/src/main/java/jp/sfjp/jindolf/log/package-info.java
new file mode 100644 (file)
index 0000000..aa5c136
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * パッケージ情報
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 olyutorskii
+ */
+
+/**
+ * ロギング関連の一連のクラス。
+ */
+
+package jp.sfjp.jindolf.log;
+
+/* EOF */
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.net;
 
 import java.net.HttpURLConnection;
 import java.net.URI;
@@ -5,7 +5,7 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.net;
 
 import java.net.URL;
 import jp.sourceforge.jindolf.parser.DecodedContent;
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.net;
 
 import java.io.IOException;
 import java.net.HttpURLConnection;
@@ -13,6 +13,8 @@ import java.net.URLConnection;
 import java.text.NumberFormat;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import jp.sfjp.jindolf.VerInfo;
+import jp.sfjp.jindolf.config.EnvInfo;
 
 /**
  * HTTP関連のユーティリティ群。
@@ -128,7 +130,7 @@ public final class HttpUtils{
      */
     public static String getUserAgentName(){
         StringBuilder result = new StringBuilder();
-        result.append(Jindolf.TITLE).append("/").append(Jindolf.VERSION);
+        result.append(VerInfo.TITLE).append("/").append(VerInfo.VERSION);
 
         StringBuilder rawComment = new StringBuilder();
         if(EnvInfo.OS_NAME != null){
@@ -5,7 +5,7 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.net;
 
 import java.awt.Component;
 import java.awt.Container;
@@ -29,6 +29,9 @@ import javax.swing.JPanel;
 import javax.swing.JRadioButton;
 import javax.swing.JTextField;
 import javax.swing.border.Border;
+import jp.sfjp.jindolf.dxchg.TextPopup;
+import jp.sfjp.jindolf.util.GUIUtils;
+import jp.sfjp.jindolf.util.Monodizer;
 
 /**
  * プロクシサーバ選択画面。
@@ -5,7 +5,7 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.net;
 
 import java.net.InetSocketAddress;
 import java.net.Proxy;
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.net;
 
 import java.awt.image.BufferedImage;
 import java.io.IOException;
@@ -23,6 +23,9 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import javax.imageio.ImageIO;
+import jp.sfjp.jindolf.data.Period;
+import jp.sfjp.jindolf.data.Village;
+import jp.sfjp.jindolf.log.LogWrapper;
 import jp.sourceforge.jindolf.parser.ContentBuilder;
 import jp.sourceforge.jindolf.parser.ContentBuilderSJ;
 import jp.sourceforge.jindolf.parser.ContentBuilderUCS2;
@@ -41,6 +44,8 @@ public class ServerAccess{
     private static final
             Map<String, SoftReference<BufferedImage>> IMAGE_CACHE;
 
+    private static final LogWrapper LOGGER = new LogWrapper();
+
     static{
         Map<String, SoftReference<BufferedImage>> cache =
                 new HashMap<String, SoftReference<BufferedImage>>();
@@ -276,7 +281,7 @@ public class ServerAccess{
         if(responseCode != HttpURLConnection.HTTP_OK){ // 200
             String logMessage =  "発言のダウンロードに失敗しました。";
             logMessage += HttpUtils.formatHttpStat(connection, 0, 0);
-            Jindolf.logger().warn(logMessage);
+            LOGGER.warn(logMessage);
             return null;
         }
 
@@ -331,7 +336,7 @@ public class ServerAccess{
         if(responseCode != HttpURLConnection.HTTP_OK){
             String logMessage =  "イメージのダウンロードに失敗しました。";
             logMessage += HttpUtils.formatHttpStat(connection, 0, 0);
-            Jindolf.logger().warn(logMessage);
+            LOGGER.warn(logMessage);
             return null;
         }
 
@@ -376,7 +381,7 @@ public class ServerAccess{
         int responseCode = connection.getResponseCode();
         if(responseCode != HttpURLConnection.HTTP_MOVED_TEMP){    // 302
             String logMessage =  "認証情報の送信に失敗しました。";
-            Jindolf.logger().warn(logMessage);
+            LOGGER.warn(logMessage);
             connection.disconnect();
             return false;
         }
@@ -390,7 +395,7 @@ public class ServerAccess{
 
         setAuthentication(loginCookie);
 
-        Jindolf.logger().info("正しく認証が行われました。");
+        LOGGER.info("正しく認証が行われました。");
 
         return true;
     }
@@ -5,12 +5,13 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.net;
 
 import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.HttpURLConnection;
+import jp.sfjp.jindolf.log.LogWrapper;
 
 /**
  * 読み込みバイト数を記録するHTTPコネクション由来のInputStream。
@@ -20,6 +21,8 @@ public class TallyInputStream extends InputStream{
 
     private static final int BUFSIZE = 2 * 1024;
 
+    private static final LogWrapper LOGGER = new LogWrapper();
+
 
     private final HttpURLConnection conn;
     private final InputStream in;
@@ -97,7 +100,7 @@ public class TallyInputStream extends InputStream{
         long span = System.nanoTime() - this.nanoLap;
 
         String message = HttpUtils.formatHttpStat(this.conn, size, span);
-        Jindolf.logger().info(message);
+        LOGGER.info(message);
 
         this.hasClosed = true;
 
@@ -5,12 +5,13 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.net;
 
 import java.io.BufferedOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.net.HttpURLConnection;
+import jp.sfjp.jindolf.log.LogWrapper;
 
 /**
  * 書き込みバイト数をログ出力するHTTPコネクション由来のOutputStream。
@@ -19,6 +20,8 @@ public class TallyOutputStream extends OutputStream{
 
     private static final int BUFSIZE = 512;
 
+    private static final LogWrapper LOGGER = new LogWrapper();
+
 
     private final HttpURLConnection conn;
     private final OutputStream out;
@@ -80,7 +83,7 @@ public class TallyOutputStream extends OutputStream{
         long span = System.nanoTime() - this.nanoLap;
 
         String message = HttpUtils.formatHttpStat(this.conn, size, span);
-        Jindolf.logger().info(message);
+        LOGGER.info(message);
 
         return;
     }
diff --git a/src/main/java/jp/sfjp/jindolf/net/package-info.java b/src/main/java/jp/sfjp/jindolf/net/package-info.java
new file mode 100644 (file)
index 0000000..1540acf
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * パッケージ情報
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 olyutorskii
+ */
+
+/**
+ * ネットワーク通信関連の一連のクラス。
+ */
+
+package jp.sfjp.jindolf.net;
+
+/* EOF */
@@ -1,13 +1,8 @@
 /*
- * Jindolf パッケージコメント
- *
- * このファイルは、SunJDK5.0以降に含まれるJavadoc用に用意された、
- * 特別な名前を持つソースファイルです。
- * このファイルはソースコードを含まず、
- * パッケージコメントとパッケージ宣言のみが含まれます。
+ * パッケージ情報
  *
  * License : The MIT License
- * Copyright(c) 2008 olyutorskii
+ * Copyright(c) 2011 olyutorskii
  */
 
 /**
@@ -57,6 +52,6 @@
  * Jindolf開発プロジェクト</a>
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf;
 
 /* EOF */
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.summary;
 
 import java.awt.Color;
 import java.awt.Component;
@@ -43,6 +43,13 @@ import javax.swing.table.DefaultTableCellRenderer;
 import javax.swing.table.DefaultTableModel;
 import javax.swing.table.TableColumn;
 import javax.swing.table.TableColumnModel;
+import jp.sfjp.jindolf.data.Avatar;
+import jp.sfjp.jindolf.data.Period;
+import jp.sfjp.jindolf.data.Talk;
+import jp.sfjp.jindolf.data.Topic;
+import jp.sfjp.jindolf.data.Village;
+import jp.sfjp.jindolf.glyph.TalkDraw;
+import jp.sfjp.jindolf.util.GUIUtils;
 import jp.sourceforge.jindolf.corelib.TalkType;
 
 /**
@@ -52,8 +59,6 @@ import jp.sourceforge.jindolf.corelib.TalkType;
 public class DaySummary extends JDialog
         implements WindowListener, ActionListener, ItemListener{
 
-    private static final String FRAMETITLE =
-            "発言集計 - " + Jindolf.TITLE;
     private static final NumberFormat AVERAGE_FORM;
     private static final String PUBTALK   = "白発言";
     private static final String WOLFTALK  = "赤発言";
@@ -90,7 +95,8 @@ public class DaySummary extends JDialog
      * @param owner オーナー
      */
     public DaySummary(Frame owner){
-        super(owner, FRAMETITLE, true);
+        super(owner);
+        setModal(true);
 
         GUIUtils.modifyWindowAttributes(this, true, false, true);
 
@@ -5,7 +5,7 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.summary;
 
 import java.net.MalformedURLException;
 import java.net.URI;
@@ -21,6 +21,16 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import jp.sfjp.jindolf.VerInfo;
+import jp.sfjp.jindolf.data.Avatar;
+import jp.sfjp.jindolf.data.Period;
+import jp.sfjp.jindolf.data.Player;
+import jp.sfjp.jindolf.data.SysEvent;
+import jp.sfjp.jindolf.data.Talk;
+import jp.sfjp.jindolf.data.Topic;
+import jp.sfjp.jindolf.data.Village;
+import jp.sfjp.jindolf.dxchg.FaceIconSet;
+import jp.sfjp.jindolf.dxchg.WolfBBS;
 import jp.sourceforge.jindolf.corelib.Destiny;
 import jp.sourceforge.jindolf.corelib.GameRole;
 import jp.sourceforge.jindolf.corelib.SysEventType;
@@ -36,6 +46,9 @@ public class GameSummary{
     public static final Comparator<Player> COMPARATOR_CASTING =
             new CastingComparator();
 
+    private static final String GENERATOR =
+            VerInfo.TITLE + "\u0020Ver." + VerInfo.VERSION;
+
 
     private final Map<Avatar, Player> playerMap =
             new HashMap<Avatar, Player>();
@@ -652,19 +665,24 @@ public class GameSummary{
         StringBuilder wikiText = new StringBuilder();
 
         String vName = this.village.getVillageFullName();
-        String generator = Jindolf.TITLE + " Ver." + Jindolf.VERSION;
         String author = iconSet.getAuthor() + "氏"
                        +" [ "+iconSet.getUrlText()+" ]";
 
         wikiText.append(WolfBBS.COMMENTLINE);
         wikiText.append("// ↓キャスト表開始\n");
-        wikiText.append("//        Village : " + vName + "\n");
-        wikiText.append("//        Generator : " + generator + "\n");
-        wikiText.append("//        アイコン作者 : " + author + '\n');
-        wikiText.append("// ※アイコン画像の著作財産権保持者"
-                       +"および画像サーバ運営者から\n");
-        wikiText.append("// 新しい意向が示された場合、"
-                       +"そちらを最優先で尊重してください。\n");
+        wikiText.append("//        Village : ")
+                .append(vName)
+                .append('\n');
+        wikiText.append("//        Generator : ")
+                .append(GENERATOR)
+                .append('\n');
+        wikiText.append("//        アイコン作者 : ")
+                .append(author)
+                .append('\n');
+        wikiText.append("// ※アイコン画像の著作財産権保持者")
+                .append("および画像サーバ運営者から\n");
+        wikiText.append("// 新しい意向が示された場合、")
+                .append("そちらを最優先で尊重してください。\n");
         wikiText.append(WolfBBS.COMMENTLINE);
 
         wikiText.append("|配役")
@@ -698,7 +716,10 @@ public class GameSummary{
             // PukiWikiではURL内の&のエスケープは不要?
 
             wikiText.append("// ========== ");
-            wikiText.append(name + " acts as [" + avatar.getName() + "]");
+            wikiText.append(name)
+                    .append(" acts as [")
+                    .append(avatar.getName())
+                    .append("]");
             wikiText.append(" ==========\n");
 
             String teamColor =  "BGCOLOR("
@@ -757,7 +778,8 @@ public class GameSummary{
         wikiText.append("RIGHT:");
         wikiText.append("顔アイコン提供 : [[");
         wikiText.append(WolfBBS.escapeWikiBracket(iconSet.getAuthor()));
-        wikiText.append(">" + iconSet.getUrlText());
+        wikiText.append(">")
+                .append(iconSet.getUrlText());
         wikiText.append("]]氏");
         wikiText.append("|\n");
 
@@ -780,12 +802,15 @@ public class GameSummary{
                                                DateFormat.FULL);
 
         String vName = this.village.getVillageFullName();
-        String generator = Jindolf.TITLE + " Ver." + Jindolf.VERSION;
 
         wikiText.append(WolfBBS.COMMENTLINE);
         wikiText.append("// ↓村詳細開始\n");
-        wikiText.append("//        Village : " + vName + "\n");
-        wikiText.append("//        Generator : " + generator + "\n");
+        wikiText.append("//        Village : ")
+                .append(vName)
+                .append('\n');
+        wikiText.append("//        Generator : ")
+                .append(GENERATOR)
+                .append('\n');
 
         wikiText.append("* 村の詳細\n");
 
@@ -5,7 +5,7 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.summary;
 
 import java.awt.Container;
 import java.awt.Dimension;
@@ -41,6 +41,17 @@ import javax.swing.JTabbedPane;
 import javax.swing.JTextArea;
 import javax.swing.JViewport;
 import javax.swing.border.Border;
+import jp.sfjp.jindolf.data.Avatar;
+import jp.sfjp.jindolf.data.Period;
+import jp.sfjp.jindolf.data.Player;
+import jp.sfjp.jindolf.data.Village;
+import jp.sfjp.jindolf.dxchg.ClipboardAction;
+import jp.sfjp.jindolf.dxchg.FaceIconSet;
+import jp.sfjp.jindolf.dxchg.TextPopup;
+import jp.sfjp.jindolf.dxchg.WebButton;
+import jp.sfjp.jindolf.dxchg.WolfBBS;
+import jp.sfjp.jindolf.util.GUIUtils;
+import jp.sfjp.jindolf.util.Monodizer;
 import jp.sourceforge.jindolf.corelib.GameRole;
 import jp.sourceforge.jindolf.corelib.Team;
 
@@ -53,8 +64,6 @@ public class VillageDigest
         implements ActionListener,
                    ItemListener {
 
-    private static final String FRAMETITLE =
-            "村のダイジェスト - " + Jindolf.TITLE;
     private static final String ITEMDELIM = " : ";
 
 
@@ -102,7 +111,8 @@ public class VillageDigest
      * @param owner 親フレーム
      */
     public VillageDigest(Frame owner){
-        super(owner, FRAMETITLE, true);
+        super(owner);
+        setModal(true);
 
         GUIUtils.modifyWindowAttributes(this, true, false, true);
 
diff --git a/src/main/java/jp/sfjp/jindolf/summary/package-info.java b/src/main/java/jp/sfjp/jindolf/summary/package-info.java
new file mode 100644 (file)
index 0000000..4d45239
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * パッケージ情報
+ *
+ * License : The MIT License
+ * Copyright(c) 2011 olyutorskii
+ */
+
+/**
+ * プレイ状況のサマライズ処理関連の一連のクラス。
+ */
+
+package jp.sfjp.jindolf.summary;
+
+/* EOF */
@@ -5,12 +5,10 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.util;
 
-import java.awt.AWTEvent;
 import java.awt.Component;
 import java.awt.Dialog;
-import java.awt.EventQueue;
 import java.awt.Frame;
 import java.awt.Point;
 import java.awt.Rectangle;
@@ -21,17 +19,15 @@ import java.awt.color.ColorSpace;
 import java.awt.image.BufferedImage;
 import java.awt.image.BufferedImageOp;
 import java.awt.image.ColorConvertOp;
-import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
-import java.net.URL;
-import javax.imageio.ImageIO;
 import javax.swing.BorderFactory;
 import javax.swing.Icon;
-import javax.swing.ImageIcon;
 import javax.swing.JComponent;
 import javax.swing.SwingConstants;
 import javax.swing.SwingUtilities;
 import javax.swing.border.Border;
+import jp.sfjp.jindolf.ResourceManager;
+import jp.sfjp.jindolf.log.LogWrapper;
 
 /**
  * GUI関連のユーティリティクラス。
@@ -62,6 +58,8 @@ public final class GUIUtils{
         public void run(){}
     };
 
+    private static final LogWrapper LOGGER = new LogWrapper();
+
     static{
         HINTS_QUALITY = new RenderingHints(null);
         HINTS_SPEEDY  = new RenderingHints(null);
@@ -136,22 +134,6 @@ public final class GUIUtils{
     }
 
     /**
-     * リソース名からイメージを取得する。
-     * @param resource リソース名
-     * @return イメージ
-     * @throws java.io.IOException 入力エラー
-     */
-    public static BufferedImage loadImageFromResource(String resource)
-            throws IOException{
-        BufferedImage result;
-
-        URL url = Jindolf.getResource(resource);
-        result = ImageIO.read(url);
-
-        return result;
-    }
-
-    /**
      * ロゴイメージを得る。
      * @return ロゴイメージ
      */
@@ -160,11 +142,10 @@ public final class GUIUtils{
             return logoImage;
         }
 
-        BufferedImage image;
-        try{
-            image = loadImageFromResource(RES_LOGOICON);
-        }catch(IOException e){
-            Jindolf.logger().warn("ロゴイメージの取得に失敗しました", e);
+        BufferedImage image =
+                ResourceManager.getBufferedImage(RES_LOGOICON);
+        if(image == null){
+            LOGGER.warn("ロゴイメージの取得に失敗しました");
             image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
             // TODO デカく "狼" とでも描くか?
         }
@@ -183,11 +164,10 @@ public final class GUIUtils{
             return windowIconImage;
         }
 
-        BufferedImage image;
-        try{
-            image = loadImageFromResource(RES_WINDOWICON);
-        }catch(IOException e){
-            Jindolf.logger().warn("アイコンイメージの取得に失敗しました", e);
+        BufferedImage image =
+                ResourceManager.getBufferedImage(RES_WINDOWICON);
+        if(image == null){
+            LOGGER.warn("アイコンイメージの取得に失敗しました");
             image = getLogoImage();
         }
 
@@ -205,8 +185,7 @@ public final class GUIUtils{
             return logoIcon;
         }
 
-        Icon icon = new ImageIcon(getLogoImage());
-
+        Icon icon = ResourceManager.getImageIcon(RES_LOGOICON);
         logoIcon = icon;
 
         return logoIcon;
@@ -221,8 +200,7 @@ public final class GUIUtils{
             return wwwIcon;
         }
 
-        URL url = Jindolf.getResource(RES_WWWICON);
-        wwwIcon = new ImageIcon(url);
+        wwwIcon = ResourceManager.getImageIcon(RES_WWWICON);
 
         return wwwIcon;
     }
@@ -236,10 +214,8 @@ public final class GUIUtils{
             return noImage;
         }
 
-        URL url = Jindolf.getResource(RES_NOIMAGE);
-        try{
-            noImage = ImageIO.read(url);
-        }catch(IOException e){
+        noImage = ResourceManager.getBufferedImage(RES_NOIMAGE);
+        if(noImage == null){
             assert false;
             noImage = getLogoImage();
         }
@@ -351,40 +327,6 @@ public final class GUIUtils{
     }
 
     /**
-     * 独自ロガーにエラーや例外を吐く、
-     * カスタム化されたイベントキューに差し替える。
-     */
-    public static void replaceEventQueue(){
-        Toolkit kit = Toolkit.getDefaultToolkit();
-        EventQueue oldQueue = kit.getSystemEventQueue();
-        EventQueue newQueue = new EventQueue(){
-            private static final String FATALMSG =
-                    "イベントディスパッチ中に異常が起きました。";
-            @Override
-            protected void dispatchEvent(AWTEvent event){
-                try{
-                    super.dispatchEvent(event);
-                }catch(RuntimeException e){
-                    Jindolf.logger().fatal(FATALMSG, e);
-                    throw e;
-                }catch(Exception e){
-                    Jindolf.logger().fatal(FATALMSG, e);
-                }catch(Error e){
-                    Jindolf.logger().fatal(FATALMSG, e);
-                    throw e;
-                }
-                // TODO Toolkit#beep()もするべきか
-                // TODO モーダルダイアログを出すべきか
-                // TODO 標準エラー出力抑止オプションを用意すべきか
-                // TODO セキュリティバイパス
-                return;
-            }
-        };
-        oldQueue.push(newQueue);
-        return;
-    }
-
-    /**
      * 任意のイメージを多階調モノクロ化する。
      * 寸法は変わらない。
      * @param image イメージ
@@ -5,7 +5,7 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.util;
 
 import java.awt.Component;
 import java.awt.Font;
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.util;
 
 import java.util.regex.Matcher;
 
diff --git a/src/main/java/jp/sfjp/jindolf/util/package-info.java b/src/main/java/jp/sfjp/jindolf/util/package-info.java
new file mode 100644 (file)
index 0000000..db3578d
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * パッケージ情報
+ *
+ * License : The MIT License
+ * Copyright(c) 2012 olyutorskii
+ */
+
+/**
+ * 各種ユーティリティクラス。
+ */
+
+package jp.sfjp.jindolf.util;
+
+/* EOF */
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.view;
 
 import java.awt.BorderLayout;
 import java.awt.Container;
@@ -33,6 +33,14 @@ import javax.swing.JSeparator;
 import javax.swing.JTextArea;
 import javax.swing.JTextField;
 import javax.swing.border.Border;
+import jp.sfjp.jindolf.VerInfo;
+import jp.sfjp.jindolf.data.Land;
+import jp.sfjp.jindolf.data.LandsModel;
+import jp.sfjp.jindolf.dxchg.TextPopup;
+import jp.sfjp.jindolf.log.LogWrapper;
+import jp.sfjp.jindolf.net.ServerAccess;
+import jp.sfjp.jindolf.util.GUIUtils;
+import jp.sfjp.jindolf.util.Monodizer;
 import jp.sourceforge.jindolf.corelib.LandState;
 
 /**
@@ -43,8 +51,8 @@ public class AccountPanel
         extends JDialog
         implements ActionListener, ItemListener{
 
-    private static final String FRAMETITLE =
-            "アカウント管理 - " + Jindolf.TITLE;
+    private static final LogWrapper LOGGER = new LogWrapper();
+
 
     private final Map<Land, String> landUserIDMap =
             new HashMap<Land, String>();
@@ -65,9 +73,11 @@ public class AccountPanel
      * @param landsModel 国モデル
      * @throws java.lang.NullPointerException 引数がnull
      */
+    @SuppressWarnings("LeakingThisInConstructor")
     public AccountPanel(Frame owner, LandsModel landsModel)
             throws NullPointerException{
-        super(owner, FRAMETITLE, true);
+        super(owner);
+        setModal(true);
 
         if(landsModel == null) throw new NullPointerException();
         for(Land land : landsModel.getLandList()){
@@ -267,7 +277,7 @@ public class AccountPanel
      * @param e ネットワークエラー
      */
     protected void showNetworkError(IOException e){
-        Jindolf.logger().warn(
+        LOGGER.warn(
                 "アカウント処理中にネットワークのトラブルが発生しました", e);
 
         Land land = getSelectedLand();
@@ -283,8 +293,8 @@ public class AccountPanel
                                            JOptionPane.WARNING_MESSAGE,
                                            JOptionPane.DEFAULT_OPTION );
 
-        JDialog dialog = pane.createDialog(this,
-                                           "通信異常発生 - " + Jindolf.TITLE);
+        String title = VerInfo.getFrameTitle("通信異常発生");
+        JDialog dialog = pane.createDialog(this, title);
 
         dialog.pack();
         dialog.setVisible(true);
@@ -313,8 +323,8 @@ public class AccountPanel
                                            JOptionPane.WARNING_MESSAGE,
                                            JOptionPane.DEFAULT_OPTION );
 
-        JDialog dialog =
-                pane.createDialog(this, "ログイン認証失敗 - " + Jindolf.TITLE);
+        String title = VerInfo.getFrameTitle("ログイン認証失敗");
+        JDialog dialog = pane.createDialog(this, title);
 
         dialog.pack();
         dialog.setVisible(true);
@@ -5,12 +5,11 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.view;
 
 import java.awt.Insets;
 import java.awt.event.ActionListener;
 import java.awt.event.KeyEvent;
-import java.net.URL;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -19,7 +18,6 @@ import javax.swing.AbstractButton;
 import javax.swing.ButtonGroup;
 import javax.swing.ButtonModel;
 import javax.swing.Icon;
-import javax.swing.ImageIcon;
 import javax.swing.JButton;
 import javax.swing.JMenu;
 import javax.swing.JMenuBar;
@@ -29,6 +27,9 @@ import javax.swing.JToolBar;
 import javax.swing.KeyStroke;
 import javax.swing.LookAndFeel;
 import javax.swing.UIManager;
+import jp.sfjp.jindolf.ResourceManager;
+import jp.sfjp.jindolf.VerInfo;
+import jp.sfjp.jindolf.util.GUIUtils;
 
 /**
  * メニュー、ボタン、その他各種Actionを伴うイベントを生成する
@@ -120,25 +121,23 @@ public class ActionManager{
             KeyStroke.getKeyStroke("ctrl F");
 
     static{
-        URL iconurl;
+        ICON_FIND =
+            ResourceManager.getImageIcon("resources/image/tb_find.png");
 
-        iconurl = Jindolf.getResource("resources/image/find.png");
-        ICON_FIND = new ImageIcon(iconurl);
+        ICON_SEARCH_PREV =
+            ResourceManager.getImageIcon("resources/image/tb_findprev.png");
 
-        iconurl = Jindolf.getResource("resources/image/findprev.png");
-        ICON_SEARCH_PREV = new ImageIcon(iconurl);
+        ICON_SEARCH_NEXT =
+            ResourceManager.getImageIcon("resources/image/tb_findnext.png");
 
-        iconurl = Jindolf.getResource("resources/image/findnext.png");
-        ICON_SEARCH_NEXT = new ImageIcon(iconurl);
+        ICON_RELOAD =
+            ResourceManager.getImageIcon("resources/image/tb_reload.png");
 
-        iconurl = Jindolf.getResource("resources/image/reload.png");
-        ICON_RELOAD = new ImageIcon(iconurl);
+        ICON_FILTER =
+            ResourceManager.getImageIcon("resources/image/tb_filter.png");
 
-        iconurl = Jindolf.getResource("resources/image/filter.png");
-        ICON_FILTER = new ImageIcon(iconurl);
-
-        iconurl = Jindolf.getResource("resources/image/editor.png");
-        ICON_EDITOR = new ImageIcon(iconurl);
+        ICON_EDITOR =
+            ResourceManager.getImageIcon("resources/image/tb_editor.png");
     }
 
     private final Set<AbstractButton> actionItems =
@@ -203,7 +202,7 @@ public class ActionManager{
         buildMenuItem(CMD_SHOWLOG, "ログ表示", KeyEvent.VK_S);
         buildMenuItem(CMD_HELPDOC, "ヘルプ表示", KeyEvent.VK_H);
         buildMenuItem(CMD_SHOWPORTAL, "ポータルサイト...", KeyEvent.VK_P);
-        buildMenuItem(CMD_ABOUT, Jindolf.TITLE + "について...", KeyEvent.VK_A);
+        buildMenuItem(CMD_ABOUT, VerInfo.TITLE + "について...", KeyEvent.VK_A);
 
         buildToolButton(CMD_RELOAD, "選択中の日を強制リロード", ICON_RELOAD);
         buildToolButton(CMD_SHOWFIND,   "検索",     ICON_FIND);
@@ -5,7 +5,7 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.view;
 
 import java.awt.Container;
 import java.awt.GridBagConstraints;
@@ -21,6 +21,7 @@ import javax.swing.JCheckBox;
 import javax.swing.JComponent;
 import javax.swing.JPanel;
 import javax.swing.border.Border;
+import jp.sfjp.jindolf.data.DialogPref;
 
 /**
  * 発言表示の各種設定パネル。
@@ -43,6 +44,7 @@ public class DialogPrefPanel
     /**
      * コンストラクタ。
      */
+    @SuppressWarnings("LeakingThisInConstructor")
     public DialogPrefPanel(){
         this.resetDefault.addActionListener(this);
         this.isSimpleMode.addItemListener(this);
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.view;
 
 import java.awt.Container;
 import java.awt.Frame;
@@ -31,6 +31,11 @@ import javax.swing.border.Border;
 import javax.swing.event.ChangeEvent;
 import javax.swing.event.ChangeListener;
 import javax.swing.event.EventListenerList;
+import jp.sfjp.jindolf.data.Avatar;
+import jp.sfjp.jindolf.data.SysEvent;
+import jp.sfjp.jindolf.data.Talk;
+import jp.sfjp.jindolf.data.Topic;
+import jp.sfjp.jindolf.util.GUIUtils;
 import jp.sourceforge.jindolf.corelib.EventFamily;
 import jp.sourceforge.jindolf.corelib.TalkType;
 
@@ -43,7 +48,6 @@ public class FilterPanel extends JDialog
 
     private static final int COLS = 4;
 
-    private static final String FRAMETITLE = "発言フィルタ - " + Jindolf.TITLE;
 
     private final JCheckBox checkPublic = new JCheckBox("公開", true);
     private final JCheckBox checkWolf = new JCheckBox("狼", true);
@@ -69,8 +73,10 @@ public class FilterPanel extends JDialog
      * 発言フィルタを生成する。
      * @param owner フレームオーナー
      */
+    @SuppressWarnings("LeakingThisInConstructor")
     public FilterPanel(Frame owner){
-        super(owner, FRAMETITLE, false);
+        super(owner);
+        setModal(false);
 
         GUIUtils.modifyWindowAttributes(this, true, false, true);
 
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.view;
 
 import java.awt.BorderLayout;
 import java.awt.Component;
@@ -51,8 +51,13 @@ import javax.swing.event.EventListenerList;
 import javax.swing.event.ListDataEvent;
 import javax.swing.event.ListDataListener;
 import javax.swing.text.JTextComponent;
+import jp.sfjp.jindolf.data.RegexPattern;
+import jp.sfjp.jindolf.dxchg.TextPopup;
+import jp.sfjp.jindolf.util.GUIUtils;
 import jp.sourceforge.jovsonz.JsArray;
+import jp.sourceforge.jovsonz.JsComposition;
 import jp.sourceforge.jovsonz.JsObject;
+import jp.sourceforge.jovsonz.JsTypes;
 import jp.sourceforge.jovsonz.JsValue;
 
 /**
@@ -65,8 +70,9 @@ public class FindPanel extends JDialog
                    ChangeListener,
                    PropertyChangeListener {
 
-    private static final String HIST_FILE = "searchHistory.json";
-    private static final String FRAMETITLE = "発言検索 - " + Jindolf.TITLE;
+    /** 検索履歴ファイル。 */
+    public static final File HIST_FILE = new File("searchHistory.json");
+
     private static final String LABEL_REENTER = "再入力";
     private static final String LABEL_IGNORE = "無視して検索をキャンセル";
 
@@ -97,8 +103,10 @@ public class FindPanel extends JDialog
      * 検索パネルを生成する。
      * @param owner 親フレーム。
      */
+    @SuppressWarnings("LeakingThisInConstructor")
     public FindPanel(Frame owner){
-        super(owner, FRAMETITLE, true);
+        super(owner);
+        setModal(true);
 
         GUIUtils.modifyWindowAttributes(this, true, false, true);
 
@@ -476,21 +484,39 @@ public class FindPanel extends JDialog
     }
 
     /**
-     * 検索履歴をロードする。
+     * JSON形式の検索履歴情報を返す。
+     * @return JSON形式の検索履歴情報
      */
-    public void loadHistory(){
-        JsValue value = ConfigFile.loadJson(new File(HIST_FILE));
-        if(value == null) return;
+    public JsObject getJson(){
+        JsObject result = new JsObject();
+
+        JsArray array = new JsArray();
+        result.putValue("history", array);
 
-        if( ! (value instanceof JsObject) ) return;
-        JsObject root = (JsObject) value;
+        List<RegexPattern> history = this.model.getOriginalHistoryList();
+        history = new ArrayList<RegexPattern>(history);
+        Collections.reverse(history);
+        for(RegexPattern regex : history){
+            JsObject obj = RegexPattern.encodeJson(regex);
+            array.add(obj);
+        }
+
+        return result;
+    }
+
+    /**
+     * JSON形式の検索履歴を反映させる。
+     * @param root JSON形式の検索履歴。nullが来たら何もしない
+     */
+    public void putJson(JsObject root){
+        if(root == null) return;
 
-        value = root.getValue("history");
-        if( ! (value instanceof JsArray) ) return;
+        JsValue value = root.getValue("history");
+        if(value.getJsTypes() != JsTypes.ARRAY) return;
         JsArray array = (JsArray) value;
 
         for(JsValue elem : array){
-            if( ! (elem instanceof JsObject) ) continue;
+            if(elem.getJsTypes() != JsTypes.OBJECT) continue;
             JsObject regObj = (JsObject) elem;
             RegexPattern regex = RegexPattern.decodeJson(regObj);
             if(regex == null) continue;
@@ -503,33 +529,15 @@ public class FindPanel extends JDialog
     }
 
     /**
-     * 検索履歴をセーブする。
+     * 起動時の履歴設定と等価か判定する。
+     * @param conf 比較対象
+     * @return 等価ならtrue
      */
-    public void saveHistory(){
-        AppSetting setting = Jindolf.getAppSetting();
-        if( ! setting.useConfigPath() ) return;
-        File configPath = setting.getConfigPath();
-        if(configPath == null) return;
-
-        JsObject root = new JsObject();
-        JsArray array = new JsArray();
-        root.putValue("history", array);
-
-        List<RegexPattern> history = this.model.getOriginalHistoryList();
-        history = new ArrayList<RegexPattern>(history);
-        Collections.reverse(history);
-        for(RegexPattern regex : history){
-            JsObject obj = RegexPattern.encodeJson(regex);
-            array.add(obj);
-        }
-
+    public boolean hasConfChanged(JsComposition<?> conf){
         if(this.loadedHistory != null){
-            if(this.loadedHistory.equals(root)) return;
+            if(this.loadedHistory.equals(conf)) return true;
         }
-
-        ConfigFile.saveJson(new File(HIST_FILE), root);
-
-        return;
+        return false;
     }
 
     /**
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.view;
 
 import java.awt.Container;
 import java.awt.GridBagConstraints;
@@ -28,6 +28,13 @@ import javax.swing.JTextArea;
 import javax.swing.border.Border;
 import javax.swing.event.HyperlinkEvent;
 import javax.swing.event.HyperlinkListener;
+import jp.sfjp.jindolf.ResourceManager;
+import jp.sfjp.jindolf.config.ConfigStore;
+import jp.sfjp.jindolf.config.EnvInfo;
+import jp.sfjp.jindolf.config.OptionInfo;
+import jp.sfjp.jindolf.dxchg.TextPopup;
+import jp.sfjp.jindolf.log.LogWrapper;
+import jp.sfjp.jindolf.util.GUIUtils;
 
 /**
  * ヘルプ画面。
@@ -38,6 +45,9 @@ public class HelpFrame extends JFrame
 
     private static final String HELP_HTML = "resources/html/help.html";
 
+    private static final LogWrapper LOGGER = new LogWrapper();
+
+
     private final JTabbedPane tabPanel = new JTabbedPane();
     private final JEditorPane htmlView = new JEditorPane();
     private final JTextArea vmInfo = new JTextArea();
@@ -45,9 +55,12 @@ public class HelpFrame extends JFrame
 
     /**
      * コンストラクタ。
+     * @param optinfo コマンドラインオプション
+     * @param configStore 設定ディレクトリ情報
      */
-    public HelpFrame(){
-        super(Jindolf.TITLE + " ヘルプ");
+    @SuppressWarnings("LeakingThisInConstructor")
+    public HelpFrame(OptionInfo optinfo, ConfigStore configStore){
+        super();
 
         GUIUtils.modifyWindowAttributes(this, true, false, true);
 
@@ -76,15 +89,22 @@ public class HelpFrame extends JFrame
             }
         });
 
-        URL topUrl = Jindolf.getResource(HELP_HTML);
+        URL topUrl = ResourceManager.getResource(HELP_HTML);
         loadURL(topUrl);
 
         StringBuilder info = new StringBuilder();
+
+        info.append("起動時引数:\n");
+        for(String arg : optinfo.getInvokeArgList()){
+            info.append("\u0020\u0020").append(arg).append('\n');
+        }
+        info.append('\n');
+
         info.append(EnvInfo.getVMInfo());
-        AppSetting setting = Jindolf.getAppSetting();
-        if(setting.useConfigPath()){
-            info.append("設定格納ディレクトリ : "
-                    + setting.getConfigPath().getPath() );
+
+        if(configStore.useStoreFile()){
+            info.append("設定格納ディレクトリ : ")
+                .append(configStore.getConfigPath().getPath());
         }else{
             info.append("※ 設定格納ディレクトリは使っていません。");
         }
@@ -146,7 +166,7 @@ public class HelpFrame extends JFrame
         try{
             this.htmlView.setPage(url);
         }catch(IOException e){
-            Jindolf.logger().warn("ヘルプファイルが読み込めません", e);
+            LOGGER.warn("ヘルプファイルが読み込めません", e);
             assert false;
         }
 
@@ -5,7 +5,7 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.view;
 
 import java.awt.GridBagConstraints;
 import java.awt.GridBagLayout;
@@ -15,6 +15,9 @@ import java.util.Date;
 import javax.swing.JComponent;
 import javax.swing.JLabel;
 import javax.swing.JPanel;
+import jp.sfjp.jindolf.data.Land;
+import jp.sfjp.jindolf.dxchg.WebButton;
+import jp.sfjp.jindolf.util.Monodizer;
 import jp.sourceforge.jindolf.corelib.LandDef;
 import jp.sourceforge.jindolf.corelib.LandState;
 
@@ -5,14 +5,13 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.view;
 
 import java.awt.BorderLayout;
 import java.awt.EventQueue;
 import java.awt.Insets;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
-import java.net.URL;
 import javax.swing.BorderFactory;
 import javax.swing.ImageIcon;
 import javax.swing.JButton;
@@ -27,6 +26,9 @@ import javax.swing.event.TreeSelectionListener;
 import javax.swing.tree.TreeModel;
 import javax.swing.tree.TreePath;
 import javax.swing.tree.TreeSelectionModel;
+import jp.sfjp.jindolf.ResourceManager;
+import jp.sfjp.jindolf.data.Land;
+import jp.sfjp.jindolf.data.LandsModel;
 
 /**
  * 国一覧Tree周辺コンポーネント群。
@@ -43,11 +45,10 @@ public class LandsTree
     private static final ImageIcon ICON_DESCEND;
 
     static{
-        URL url;
-        url = Jindolf.getResource("resources/image/ascend.png");
-        ICON_ASCEND = new ImageIcon(url);
-        url = Jindolf.getResource("resources/image/descend.png");
-        ICON_DESCEND = new ImageIcon(url);
+        ICON_ASCEND =
+            ResourceManager.getImageIcon("resources/image/tb_ascend.png");
+        ICON_DESCEND =
+            ResourceManager.getImageIcon("resources/image/tb_descend.png");
     }
 
     private final JButton orderButton = new JButton();
@@ -59,6 +60,7 @@ public class LandsTree
     /**
      * コンストラクタ。
      */
+    @SuppressWarnings("LeakingThisInConstructor")
     public LandsTree(){
         super();
 
@@ -70,9 +72,9 @@ public class LandsTree
         this.orderButton.setActionCommand(ActionManager.CMD_SWITCHORDER);
         this.orderButton.addActionListener(this);
 
-        URL url;
-        url = Jindolf.getResource("resources/image/reload.png");
-        this.reloadButton.setIcon(new ImageIcon(url));
+        ImageIcon icon =
+                ResourceManager.getImageIcon("resources/image/tb_reload.png");
+        this.reloadButton.setIcon(icon);
         this.reloadButton.setToolTipText(TIP_ORDER);
         this.reloadButton.setMargin(new Insets(1, 1, 1, 1));
         this.reloadButton.setActionCommand(ActionManager.CMD_VILLAGELIST);
diff --git a/src/main/java/jp/sfjp/jindolf/view/LockErrorPane.java b/src/main/java/jp/sfjp/jindolf/view/LockErrorPane.java
new file mode 100644 (file)
index 0000000..3ec7a3b
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+ * lock file warning dialog
+ *
+ * License : The MIT License
+ * Copyright(c) 2012 olyutorskii
+ */
+
+package jp.sfjp.jindolf.view;
+
+import java.awt.Component;
+import java.awt.HeadlessException;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JOptionPane;
+import javax.swing.JRadioButton;
+import jp.sfjp.jindolf.config.FileUtils;
+import jp.sfjp.jindolf.config.InterVMLock;
+
+/**
+ * ロックエラー用ダイアログ。
+ * <ul>
+ * <li>強制解除
+ * <li>リトライ
+ * <li>設定ディレクトリを無視
+ * <li>起動中止
+ * </ul>
+ * の選択を利用者に求める。
+ */
+@SuppressWarnings("serial")
+public class LockErrorPane extends JOptionPane implements ActionListener{
+
+    private final InterVMLock lock;
+
+    private final JRadioButton continueButton =
+            new JRadioButton("設定ディレクトリを使わずに起動を続行");
+    private final JRadioButton retryButton =
+            new JRadioButton("再度ロック取得を試す");
+    private final JRadioButton forceButton =
+            new JRadioButton(
+            "<html>"
+            + "ロックを強制解除<br>"
+            + " (※他のJindolfと設定ファイル書き込みが衝突するかも…)"
+            + "</html>");
+
+    private final JButton okButton = new JButton("OK");
+    private final JButton abortButton = new JButton("起動中止");
+
+    private boolean aborted = false;
+
+    /**
+     * コンストラクタ。
+     * @param lock 失敗したロック
+     */
+    @SuppressWarnings("LeakingThisInConstructor")
+    public LockErrorPane(InterVMLock lock){
+        super();
+
+        this.lock = lock;
+
+        String htmlMessage =
+                "<html>"
+                + "設定ディレクトリのロックファイル<br>"
+                + "<center>[&nbsp;"
+                + FileUtils.getHtmledFileName(this.lock.getLockFile())
+                + "&nbsp;]</center>"
+                + "<br>"
+                + "のロックに失敗しました。<br>"
+                + "考えられる原因としては、<br>"
+                + "<ul>"
+                + "<li>前回起動したJindolfの終了が正しく行われなかった"
+                + "<li>今どこかで他のJindolfが動いている"
+                + "</ul>"
+                + "などが考えられます。<br>"
+                + "<hr>"
+                + "</html>";
+
+        ButtonGroup bgrp = new ButtonGroup();
+        bgrp.add(this.continueButton);
+        bgrp.add(this.retryButton);
+        bgrp.add(this.forceButton);
+        this.continueButton.setSelected(true);
+
+        Object[] msg = {
+            htmlMessage,
+            this.continueButton,
+            this.retryButton,
+            this.forceButton,
+        };
+        setMessage(msg);
+
+        Object[] opts = {
+            this.okButton,
+            this.abortButton,
+        };
+        setOptions(opts);
+
+        setMessageType(JOptionPane.ERROR_MESSAGE);
+
+        this.okButton   .addActionListener(this);
+        this.abortButton.addActionListener(this);
+
+        return;
+    }
+
+    /**
+     * 「設定ディレクトリを無視して続行」が選択されたか判定する。
+     * @return 「無視して続行」が選択されていればtrue
+     */
+    public boolean isRadioContinue(){
+        return this.continueButton.isSelected();
+    }
+
+    /**
+     * 「リトライ」が選択されたか判定する。
+     * @return 「リトライ」が選択されていればtrue
+     */
+    public boolean isRadioRetry(){
+        return this.retryButton.isSelected();
+    }
+
+    /**
+     * 「強制解除」が選択されたか判定する。
+     * @return 「強制解除」が選択されていればtrue
+     */
+    public boolean isRadioForce(){
+        return this.forceButton.isSelected();
+    }
+
+    /**
+     * 「起動中止」が選択されたか判定する。
+     * @return 「起動中止」が押されていたならtrue
+     */
+    public boolean isAborted(){
+        return this.aborted;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param parentComponent {@inheritDoc}
+     * @param title {@inheritDoc}
+     * @return {@inheritDoc}
+     * @throws HeadlessException {@inheritDoc}
+     */
+    @Override
+    public JDialog createDialog(Component parentComponent,
+                                    String title)
+            throws HeadlessException{
+        final JDialog dialog =
+                super.createDialog(parentComponent, title);
+
+        ActionListener listener = new ActionListener(){
+            public void actionPerformed(ActionEvent event){
+                dialog.setVisible(false);
+                return;
+            }
+        };
+
+        this.okButton   .addActionListener(listener);
+        this.abortButton.addActionListener(listener);
+
+        return dialog;
+    }
+
+    /**
+     * ボタン押下を受信する。
+     * @param event イベント
+     */
+    public void actionPerformed(ActionEvent event){
+        Object source = event.getSource();
+        if(source == this.okButton) this.aborted = false;
+        else                        this.aborted = true;
+        return;
+    }
+
+}
@@ -5,7 +5,7 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.view;
 
 import java.awt.Container;
 import java.awt.Frame;
@@ -20,6 +20,10 @@ import javax.swing.JButton;
 import javax.swing.JDialog;
 import javax.swing.JSeparator;
 import javax.swing.JTabbedPane;
+import jp.sfjp.jindolf.glyph.FontChooser;
+import jp.sfjp.jindolf.glyph.FontInfo;
+import jp.sfjp.jindolf.net.ProxyChooser;
+import jp.sfjp.jindolf.util.GUIUtils;
 
 /**
  * オプション設定パネル。
@@ -29,9 +33,6 @@ public class OptionPanel
         extends JDialog
         implements ActionListener, WindowListener{
 
-    private static final String FRAMETITLE =
-            "オプション設定 - " + Jindolf.TITLE;
-
     private final JTabbedPane tabPane = new JTabbedPane();
 
     private final FontChooser fontChooser;
@@ -47,8 +48,10 @@ public class OptionPanel
      * コンストラクタ。
      * @param owner フレームオーナ
      */
+    @SuppressWarnings("LeakingThisInConstructor")
     public OptionPanel(Frame owner){
-        super(owner, FRAMETITLE, true);
+        super(owner);
+        setModal(true);
 
         GUIUtils.modifyWindowAttributes(this, true, false, true);
 
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.view;
 
 import java.awt.BorderLayout;
 import java.awt.Color;
@@ -30,6 +30,14 @@ import javax.swing.JScrollPane;
 import javax.swing.JViewport;
 import javax.swing.ScrollPaneConstants;
 import javax.swing.border.Border;
+import jp.sfjp.jindolf.data.DialogPref;
+import jp.sfjp.jindolf.data.Period;
+import jp.sfjp.jindolf.data.Talk;
+import jp.sfjp.jindolf.data.Topic;
+import jp.sfjp.jindolf.data.Village;
+import jp.sfjp.jindolf.glyph.Discussion;
+import jp.sfjp.jindolf.glyph.FontInfo;
+import jp.sfjp.jindolf.glyph.TalkDraw;
 import jp.sourceforge.jindolf.corelib.TalkType;
 
 /**
@@ -57,6 +65,7 @@ public class PeriodView extends JPanel implements ItemListener{
      * 発言ブラウザを内包するPeriodビューワを生成する。
      * @param period 日
      */
+    @SuppressWarnings("LeakingThisInConstructor")
     public PeriodView(Period period){
         super();
 
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.view;
 
 import java.awt.Component;
 import java.awt.event.ActionListener;
@@ -19,6 +19,12 @@ import javax.swing.JTabbedPane;
 import javax.swing.SwingConstants;
 import javax.swing.border.Border;
 import javax.swing.event.EventListenerList;
+import jp.sfjp.jindolf.data.DialogPref;
+import jp.sfjp.jindolf.data.Period;
+import jp.sfjp.jindolf.data.Village;
+import jp.sfjp.jindolf.glyph.AnchorHitListener;
+import jp.sfjp.jindolf.glyph.Discussion;
+import jp.sfjp.jindolf.glyph.FontInfo;
 
 /**
  * タブを用いて村情報と各Periodを閲覧するためのコンポーネント。
@@ -5,7 +5,7 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.view;
 
 import java.awt.BorderLayout;
 import java.awt.CardLayout;
@@ -26,6 +26,10 @@ import javax.swing.SwingConstants;
 import javax.swing.border.BevelBorder;
 import javax.swing.border.Border;
 import javax.swing.border.CompoundBorder;
+import jp.sfjp.jindolf.VerInfo;
+import jp.sfjp.jindolf.data.Land;
+import jp.sfjp.jindolf.data.Village;
+import jp.sfjp.jindolf.util.GUIUtils;
 
 /**
  * 最上位ビュー。
@@ -38,6 +42,11 @@ public class TopView extends JPanel{
     private static final String LANDCARD = "LANDINFO";
     private static final String BROWSECARD = "BROWSER";
 
+    private static final String MSG_THANKS =
+            VerInfo.TITLE + "\u0020" + VerInfo.VERSION
+            + "\u0020を使ってくれてありがとう!";
+
+
     private final JComponent cards;
     private final CardLayout cardLayout = new CardLayout();
 
@@ -185,9 +194,7 @@ public class TopView extends JPanel{
      * @return ステータスバー
      */
     private JComponent createStatusBar(){
-        this.sysMessage.setText(
-                  Jindolf.TITLE + " " + Jindolf.VERSION
-                + " を使ってくれてありがとう!" );
+        this.sysMessage.setText(MSG_THANKS);
         this.sysMessage.setEditable(false);
         Border inside  = BorderFactory.createBevelBorder(BevelBorder.LOWERED);
         Border outside = BorderFactory.createEmptyBorder(2, 5, 2, 2);
@@ -5,7 +5,9 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.view;
+
+import jp.sfjp.jindolf.data.Topic;
 
 /**
  * 発言Topicのフィルタリングを行うインタフェース。
@@ -13,11 +15,6 @@ package jp.sourceforge.jindolf;
 public interface TopicFilter {
 
     /**
-     * フィルタの状態を表すインタフェース。
-     */
-    interface FilterContext{}
-
-    /**
      * 与えられたTopicをフィルタリングする。
      * @param topic Topic
      * @return フィルタリングするならtrue
@@ -38,4 +35,9 @@ public interface TopicFilter {
      */
     boolean isSame(FilterContext context);
 
+    /**
+     * フィルタの状態を表すインタフェース。
+     */
+    interface FilterContext{}
+
 }
@@ -5,13 +5,14 @@
  * Copyright(c) 2008 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.view;
 
 import java.awt.Component;
-import java.net.URL;
 import javax.swing.ImageIcon;
 import javax.swing.JTree;
 import javax.swing.tree.DefaultTreeCellRenderer;
+import jp.sfjp.jindolf.ResourceManager;
+import jp.sfjp.jindolf.data.Village;
 
 /**
  * JTreeの村別アイコン表示。
@@ -26,17 +27,16 @@ public class VillageIconRenderer extends DefaultTreeCellRenderer{
     private static final ImageIcon ICON_INVALID;
 
     static{
-        URL url;
-        url = Jindolf.getResource("resources/image/prologue.png");
-        ICON_PROLOGUE = new ImageIcon(url);
-        url = Jindolf.getResource("resources/image/progress.png");
-        ICON_PROGRESS = new ImageIcon(url);
-        url = Jindolf.getResource("resources/image/epilogue.png");
-        ICON_EPILOGUE = new ImageIcon(url);
-        url = Jindolf.getResource("resources/image/gameover.png");
-        ICON_GAMEOVER = new ImageIcon(url);
-        url = Jindolf.getResource("resources/image/cross.png");
-        ICON_INVALID = new ImageIcon(url);
+        ICON_PROLOGUE =
+            ResourceManager.getImageIcon("resources/image/vs_prologue.png");
+        ICON_PROGRESS =
+            ResourceManager.getImageIcon("resources/image/vs_progress.png");
+        ICON_EPILOGUE =
+            ResourceManager.getImageIcon("resources/image/vs_epilogue.png");
+        ICON_GAMEOVER =
+            ResourceManager.getImageIcon("resources/image/vs_gameover.png");
+        ICON_INVALID =
+            ResourceManager.getImageIcon("resources/image/vs_cross.png");
     }
 
     /**
@@ -5,13 +5,14 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.view;
 
 import java.awt.GridBagConstraints;
 import java.awt.GridBagLayout;
 import java.awt.Insets;
 import javax.swing.JLabel;
 import javax.swing.JPanel;
+import jp.sfjp.jindolf.data.Village;
 
 /**
  * 村情報表示パネル。
diff --git a/src/main/java/jp/sfjp/jindolf/view/package-info.java b/src/main/java/jp/sfjp/jindolf/view/package-info.java
new file mode 100644 (file)
index 0000000..98bd551
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * パッケージ情報
+ *
+ * License : The MIT License
+ * Copyright(c) 2012 olyutorskii
+ */
+
+/**
+ * 各種ビュークラス。
+ */
+
+package jp.sfjp.jindolf.view;
+
+/* EOF */
diff --git a/src/main/java/jp/sourceforge/jindolf/Jindolf.java b/src/main/java/jp/sourceforge/jindolf/Jindolf.java
deleted file mode 100644 (file)
index fee2ae0..0000000
+++ /dev/null
@@ -1,817 +0,0 @@
-/*
- * Jindolf main class
- *
- * License : The MIT License
- * Copyright(c) 2008 olyutorskii
- */
-
-package jp.sourceforge.jindolf;
-
-import java.awt.Dimension;
-import java.awt.EventQueue;
-import java.awt.GraphicsEnvironment;
-import java.awt.Window;
-import java.io.BufferedInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.LineNumberReader;
-import java.io.Reader;
-import java.lang.reflect.InvocationTargetException;
-import java.net.URL;
-import java.security.Permission;
-import java.text.DateFormat;
-import java.text.NumberFormat;
-import java.util.Date;
-import java.util.Properties;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.logging.ConsoleHandler;
-import java.util.logging.Handler;
-import java.util.logging.Logger;
-import java.util.logging.LoggingPermission;
-import javax.swing.ImageIcon;
-import javax.swing.JFrame;
-import javax.swing.JLabel;
-import javax.swing.JOptionPane;
-import javax.swing.JWindow;
-import javax.swing.UIManager;
-
-/**
- * Jindolf スタートアップクラス。
- *
- * コンストラクタは無いよ。
- * アプリ開始はstaticメソッド{@link #main(String[])}呼び出しから。
- */
-public final class Jindolf{
-
-    /** 実行に最低限必要なJREの版数。 */
-    public static final String MINIMUM_JREVER = "1.5";
-
-
-    /** このClass。 */
-    public static final Class<?>        SELF_KLASS;
-    /** このPackage。 */
-    public static final Package         SELF_PACKAGE;
-    /** ランタイムPackage。 */
-    public static final Package         JRE_PACKAGE;
-    /** 実行環境。 */
-    public static final Runtime         RUNTIME;
-    /** セキュリティマネージャ。 */
-    public static final SecurityManager SEC_MANAGER;
-    /** クラスローダ。 */
-    public static final ClassLoader     LOADER;
-
-
-    /** クラスロード時のナノカウント。 */
-    public static final long NANOCT_LOADED;
-    /** クラスロード時刻(エポックmsec)。 */
-    public static final long EPOCHMS_LOADED;
-
-
-    /** タイトル。 */
-    public static final String TITLE;
-    /** バージョン。 */
-    public static final String VERSION;
-    /** 作者名。 */
-    public static final String AUTHOR;
-    /** 著作権表記。 */
-    public static final String COPYRIGHT;
-    /** ライセンス表記。 */
-    public static final String LICENSE;
-    /** 連絡先。 */
-    public static final String CONTACT;
-    /** 初出。 */
-    public static final String DEBUT;
-    /** その他、何でも書きたいこと。 */
-    public static final String COMMENT;
-    /** クレジット。 */
-    public static final String ID;
-
-    /** 共通ロガー。 */
-    private static final LogWrapper COMMON_LOGGER;
-
-    /** 多重起動防止用セマフォ。 */
-    private static final AtomicBoolean INVOKE_FLAG;
-
-    /** スプラッシュロゴ。 */
-    private static final String RES_LOGOICON =
-            "resources/image/logo.png";
-
-    private static OptionInfo option;
-    private static AppSetting setting;
-
-    /** バージョン定義リソース。 */
-    private static final String RES_VERDEF = "resources/version.properties";
-
-    static{
-        SELF_KLASS   = Jindolf.class;
-        SELF_PACKAGE = SELF_KLASS.getPackage();
-        JRE_PACKAGE  = java.lang.Object.class.getPackage();
-        RUNTIME      = Runtime.getRuntime();
-        SEC_MANAGER  = System.getSecurityManager();
-
-        ClassLoader thisLoader;
-        try{
-            thisLoader = SELF_KLASS.getClassLoader();
-        }catch(SecurityException e){
-            thisLoader = null;
-        }
-        LOADER = thisLoader;
-
-        if( ! JRE_PACKAGE.isCompatibleWith(MINIMUM_JREVER) ){
-            String jreInstalled;
-            try{
-                jreInstalled = System.getProperty("java.home");
-            }catch(SecurityException e){
-                jreInstalled = "※インストール位置不明";
-            }
-            String errmsg =
-                    "今このプログラム " + SELF_KLASS.getName() + " は\n"
-                    +"[ " + jreInstalled
-                    +" ]\nにインストールされた"
-                    +" JRE" + JRE_PACKAGE.getSpecificationVersion()
-                    +" の実行環境で実行されようとしました。\n"
-                    +"しかしこのプログラムの実行には"
-                    +" JRE" + MINIMUM_JREVER
-                    + " 以降の実行環境が必要です。\n"
-                    +"おそらく http://www.java.com/ などからの"
-                    +"入手が可能でしょう。";
-
-            errorDialog("実行系の不備", errmsg);
-
-            RUNTIME.exit(1);
-        }
-
-        // ここからJRE1.5解禁。
-
-        NANOCT_LOADED  = System.nanoTime();
-        EPOCHMS_LOADED = System.currentTimeMillis();
-
-        Properties verProp = loadVersionDefinition(SELF_KLASS);
-        TITLE   = getPackageInfo(verProp, "pkg-title.",   "Unknown");
-        VERSION = getPackageInfo(verProp, "pkg-version.", "0");
-        AUTHOR  = getPackageInfo(verProp, "pkg-author.",  "nobody");
-        LICENSE = getPackageInfo(verProp, "pkg-license.", "Unknown");
-        CONTACT = getPackageInfo(verProp, "pkg-contact.", "Unknown");
-        DEBUT   = getPackageInfo(verProp, "pkg-debut.",   "2008");
-        COMMENT = getPackageInfo(verProp, "pkg-comment.", "");
-        COPYRIGHT = "Copyright(c)" +"\u0020"+ DEBUT +"\u0020"+ AUTHOR;
-        ID = TITLE
-            +"\u0020"+ "Ver." + VERSION
-            +"\u0020"+ COPYRIGHT
-            +"\u0020"+ "("+ LICENSE +")";
-
-        Logger jre14Logger = Logger.getLogger(SELF_PACKAGE.getName());
-        COMMON_LOGGER = new LogWrapper(jre14Logger);
-
-        INVOKE_FLAG = new AtomicBoolean(false);
-
-        new Jindolf();
-    }
-
-
-    /**
-     * 隠れコンストラクタ。
-     */
-    private Jindolf(){
-        super();
-        assert this.getClass() == SELF_KLASS;
-        return;
-    }
-
-
-    /**
-     * 起動オプション情報を返す。
-     * @return 起動オプション情報
-     */
-    public static OptionInfo getOptionInfo(){
-        return option;
-    }
-
-    /**
-     * アプリ設定を返す。
-     * @return アプリ設定
-     */
-    public static AppSetting getAppSetting(){
-        return setting;
-    }
-
-    /**
-     * エラーダイアログをビットマップディスプレイに出現させる。
-     * メインウィンドウが整備されるまでの間だけ一時的に使う。
-     * 努力目標:なるべく昔のJRE環境でも例外無く動くように。
-     * @param title タイトル
-     * @param message メッセージ
-     */
-    private static void errorDialog(String title, String message){
-        System.err.println(message);
-        System.err.flush();
-
-        if( ! JRE_PACKAGE.isCompatibleWith("1.2") ){
-            return;
-        }
-
-        if(   JRE_PACKAGE.isCompatibleWith("1.4")
-           && GraphicsEnvironment.isHeadless()){
-            return;
-        }
-
-        JOptionPane.showMessageDialog(null,
-                                      message,
-                                      title,
-                                      JOptionPane.ERROR_MESSAGE);
-
-        return;
-    }
-
-    /**
-     * リソース上のパッケージ定義プロパティをロードする。
-     * MANIFEST.MFが参照できない実行環境での代替品。
-     * @param klass パッケージを構成する任意のクラス
-     * @return プロパティ
-     */
-    private static Properties loadVersionDefinition(Class klass){
-        Properties result = new Properties();
-
-        InputStream istream = klass.getResourceAsStream(RES_VERDEF);
-        try{
-            result.load(istream);
-        }catch(IOException e){
-            return result;
-        }finally{
-            try{
-                istream.close();
-            }catch(IOException e){
-                return result;
-            }
-        }
-
-        return result;
-    }
-
-    /**
-     * リソース上のプロパティから
-     * このクラスのパッケージのパッケージ情報を取得する。
-     * MANIFEST.MFが参照できない実行環境での代替品。
-     * @param prop プロパティ
-     * @param prefix 接頭辞
-     * @param defValue 見つからなかった場合のデフォルト値
-     * @return パッケージ情報
-     */
-    public static String getPackageInfo(Properties prop,
-                                          String prefix,
-                                          String defValue){
-        return getPackageInfo(prop, SELF_PACKAGE, prefix, defValue);
-    }
-
-    /**
-     * リソース上のプロパティからパッケージ情報を取得する。
-     * MANIFEST.MFが参照できない実行環境での代替品。
-     * @param prop プロパティ
-     * @param pkg 任意のパッケージ
-     * @param prefix 接頭辞
-     * @param defValue デフォルト値
-     * @return 見つからなかった場合のパッケージ情報
-     */
-    public static String getPackageInfo(Properties prop,
-                                          Package pkg,
-                                          String prefix,
-                                          String defValue){
-        String propName = prefix + pkg.getName();
-        String result = prop.getProperty(propName, defValue);
-        return result;
-    }
-
-    /**
-     * Jindolf の実行が可能なGUI環境でなければ、即時VM実行を終了する。
-     */
-    private static void checkGUIEnvironment(){
-        if(GraphicsEnvironment.isHeadless()){
-            System.err.println(
-                    TITLE
-                    + " はGUI環境と接続できませんでした");
-
-            String dispEnv;
-            try{
-                dispEnv = System.getenv("DISPLAY");
-            }catch(SecurityException e){
-                dispEnv = null;
-            }
-
-            // for X11 user
-            if(dispEnv != null){
-                System.err.println("環境変数 DISPLAY : " + dispEnv);
-            }
-
-            RUNTIME.exit(1);
-        }
-
-        return;
-    }
-
-    /**
-     * コンパイル時のエラーを判定する。
-     * ※ 非Unicode系の開発環境を使いたい人は適当に無視してね。
-     */
-    private static void checkCompileError(){
-        String errmsg =
-                "ソースコードの文字コードが"
-               +"正しくコンパイルされていないかも。\n"
-               +"あなたは今、オリジナル開発元の意図しない文字コード環境で"
-               +"コンパイルされたプログラムを起動しようとしているよ。\n"
-               +"ソースコードの入手に際して"
-               +"どのような文字コード変換が行われたか認識しているかな?\n"
-               +"コンパイルオプションで正しい文字コードを指定したかな?";
-
-        if(   '狼' != 0x72fc
-           || ' ' != 0x3000
-           || '~'  != 0x007e
-           || '\\' != 0x005c  // バックスラッシュ
-           || '¥'  != 0x00a5  // 半角円通貨
-           || '~' != 0xff5e
-           || '�' != 0xfffd  // Unicode専用特殊文字
-           ){
-            JOptionPane.showMessageDialog(null,
-                                          errmsg,
-                                          "コンパイルの不備",
-                                          JOptionPane.ERROR_MESSAGE);
-            RUNTIME.exit(1);
-        }
-        return;
-    }
-
-    /**
-     * MANIFEST.MFパッケージ定義エラーの検出。
-     * ビルド前にMANIFEST自動生成Antタスク「manifest」を忘れてないかい?
-     */
-    private static void checkPackageDefinition(){
-        String implTitle   = SELF_PACKAGE.getImplementationTitle();
-        String implVersion = SELF_PACKAGE.getImplementationVersion();
-        String implVendor  = SELF_PACKAGE.getImplementationVendor();
-
-        String errmsg = null;
-
-        if(   implTitle != null
-           && ! implTitle.equals(TITLE) ){
-            errmsg = "パッケージ定義とタイトルが一致しません。"
-                    +"["+ implTitle +"]≠["+ TITLE +"]";
-        }else if(   implVersion != null
-                 && ! implVersion.equals(VERSION) ){
-            errmsg = "パッケージ定義とバージョン番号が一致しません。"
-                    +"["+ implVersion +"]≠["+ VERSION +"]";
-        }else if(   implVendor != null
-                 && ! implVendor.equals(AUTHOR) ){
-            errmsg = "パッケージ定義とベンダが一致しません。"
-                    +"["+ implVendor +"]≠["+ AUTHOR +"]";
-        }
-
-        if(errmsg != null){
-            JOptionPane.showMessageDialog(null,
-                                          errmsg,
-                                          "ビルドエラー",
-                                          JOptionPane.ERROR_MESSAGE);
-            RUNTIME.exit(1);
-        }
-
-        return;
-    }
-
-    /**
-     * 標準出力端末にヘルプメッセージ(オプションの説明)を表示する。
-     */
-    private static void showHelpMessage(){
-        System.out.flush();
-        System.err.flush();
-
-        CharSequence helpText = CmdOption.getHelpText();
-        System.out.print(helpText);
-
-        System.out.flush();
-        System.err.flush();
-
-        return;
-    }
-
-    /**
-     * スプラッシュウィンドウを作成する。
-     * JRE1.6以降では呼ばれないはず。
-     * @return 未表示のスプラッシュウィンドウ。
-     */
-    private static Window createSplashWindow(){
-        Window splashWindow = new JWindow();
-
-        URL url = getResource(RES_LOGOICON);
-        ImageIcon logo = new ImageIcon(url);
-        JLabel splashLabel = new JLabel(logo);
-
-        splashWindow.add(splashLabel);
-        splashWindow.pack();
-        splashWindow.setLocationRelativeTo(null); // locate center
-
-        return splashWindow;
-    }
-
-    /**
-     * ロギング初期化。
-     * @param useConsoleLog trueならConsoleHandlerを使う。
-     */
-    private static void initLogging(boolean useConsoleLog){
-        boolean hasPermission = hasLoggingPermission();
-
-        if( ! hasPermission){
-            System.out.println(
-                      "セキュリティ設定により、"
-                    + "ログ設定を変更できませんでした" );
-        }
-
-        Logger jre14Logger = COMMON_LOGGER.getJre14Logger();
-
-        if(hasPermission){
-            jre14Logger.setUseParentHandlers(false);
-            Handler pileHandler = new PileHandler();
-            jre14Logger.addHandler(pileHandler);
-        }
-
-        if(hasPermission && useConsoleLog){
-            Handler consoleHandler = new ConsoleHandler();
-            jre14Logger.addHandler(consoleHandler);
-        }
-
-        return;
-    }
-
-    /**
-     * ログ操作のアクセス権があるか否か判定する。
-     * @return アクセス権があればtrue
-     */
-    public static boolean hasLoggingPermission(){
-        if(SEC_MANAGER == null) return true;
-
-        Permission logPermission = new LoggingPermission("control", null);
-        try{
-            SEC_MANAGER.checkPermission(logPermission);
-        }catch(SecurityException e){
-            return false;
-        }
-
-        return true;
-    }
-
-    /**
-     * 起動時の諸々の情報をログ出力する。
-     */
-    private static void dumpBootInfo(){
-        DateFormat dform = DateFormat.getDateTimeInstance();
-        NumberFormat nform = NumberFormat.getNumberInstance();
-
-        logger().info(
-                ID + " は "
-                + dform.format(new Date(EPOCHMS_LOADED))
-                + " にVM上のクラス "
-                + SELF_KLASS.getName() + " としてロードされました。 " );
-
-        logger().info("Initial Nano-Count : " + nform.format(NANOCT_LOADED));
-
-        logger().info(
-                "Max-heap : "
-                + nform.format(RUNTIME.maxMemory()) + " Byte"
-                + "   Total-heap : "
-                + nform.format(RUNTIME.totalMemory()) + " Byte");
-
-        logger().info("\n" + EnvInfo.getVMInfo());
-
-        if(getAppSetting().useConfigPath()){
-            logger().info("設定格納ディレクトリに[ "
-                    + getAppSetting().getConfigPath().getPath()
-                    + " ]が指定されました。");
-        }else{
-            logger().info("設定格納ディレクトリは使いません。");
-        }
-
-        if(   JRE_PACKAGE.isCompatibleWith("1.6")
-           && option.hasOption(CmdOption.OPT_NOSPLASH) ){
-            logger().warn(
-                      "JRE1.6以降では、"
-                    +"Jindolfの-nosplashオプションは無効です。"
-                    + "Java実行系の方でスプラッシュ画面の非表示を"
-                    + "指示してください(おそらく空の-splash:オプション)" );
-        }
-
-        if(LOADER == null){
-            logger().warn(
-                    "セキュリティ設定により、"
-                    +"クラスローダを取得できませんでした");
-        }
-
-        return;
-    }
-
-    /**
-     * 任意のクラス群に対して一括ロード/初期化を単一スレッドで順に行う。
-     * どーしてもクラス初期化の順序に依存する障害が発生する場合や
-     * クラス初期化のオーバーヘッドでGUIの操作性が損なわれるときなどにどうぞ。
-     *
-     * @throws java.lang.LinkageError クラス間リンケージエラー。
-     * @throws java.lang.ExceptionInInitializerError クラス初期化で異常
-     */
-    private static void preInitClass()
-            throws LinkageError,
-                   ExceptionInInitializerError {
-        Object[] classes = {            // Class型 または String型
-            "java.lang.Object",
-            TabBrowser.class,
-            Discussion.class,
-            GlyphDraw.class,
-            java.net.HttpURLConnection.class,
-            java.text.SimpleDateFormat.class,
-            Void.class,
-        };
-
-        for(Object obj : classes){
-            String className;
-            if(obj instanceof Class){
-                className = ((Class<?>)obj).getName();
-            }else if(obj instanceof String){
-                className = obj.toString();
-            }else{
-                continue;
-            }
-
-            try{
-                if(LOADER != null){
-                    Class.forName(className, true, LOADER);
-                }else{
-                    Class.forName(className);
-                }
-            }catch(ClassNotFoundException e){
-                logger().warn("クラスの明示的ロードに失敗しました", e);
-                continue;
-            }
-        }
-
-        return;
-    }
-
-    /**
-     * AWTイベントディスパッチスレッド版スタートアップエントリ。
-     */
-    private static void startGUI(){
-        LandsModel model = new LandsModel();
-        model.loadLandList();
-
-        JFrame topFrame = buildMVC(model);
-
-        GUIUtils.modifyWindowAttributes(topFrame, true, false, true);
-
-        topFrame.pack();
-
-        Dimension initGeometry =
-                new Dimension(setting.initialFrameWidth(),
-                              setting.initialFrameHeight());
-        topFrame.setSize(initGeometry);
-
-        if(   setting.initialFrameXpos() <= Integer.MIN_VALUE
-           || setting.initialFrameYpos() <= Integer.MIN_VALUE ){
-            topFrame.setLocationByPlatform(true);
-        }else{
-            topFrame.setLocation(setting.initialFrameXpos(),
-                                 setting.initialFrameYpos() );
-        }
-
-        topFrame.setVisible(true);
-
-        return;
-    }
-
-    /**
-     * モデル・ビュー・コントローラの結合。
-     *
-     * @param model 最上位のデータモデル
-     * @return アプリケーションのトップフレーム
-     */
-    private static JFrame buildMVC(LandsModel model){
-        ActionManager actionManager = new ActionManager();
-        TopView topView = new TopView();
-
-        Controller controller = new Controller(actionManager, topView, model);
-
-        JFrame topFrame = controller.createTopFrame();
-
-        return topFrame;
-    }
-
-    /**
-     * リソースからUTF-8で記述されたテキストデータをロードする。
-     * @param resourceName リソース名
-     * @return テキスト文字列
-     * @throws java.io.IOException 入出力の異常。おそらくビルドミス。
-     */
-    public static CharSequence loadResourceText(String resourceName)
-            throws IOException{
-        InputStream is;
-        is = getResourceAsStream(resourceName);
-        is = new BufferedInputStream(is);
-        Reader reader = new InputStreamReader(is, "UTF-8");
-        LineNumberReader lineReader = new LineNumberReader(reader);
-
-        StringBuilder result = new StringBuilder();
-        try{
-            for(;;){
-                String line = lineReader.readLine();
-                if(line == null) break;
-                if(line.startsWith("#")) continue;
-                result.append(line).append('\n');
-            }
-        }finally{
-            lineReader.close();
-        }
-
-        return result;
-    }
-
-    /**
-     * クラスローダを介してリソースからの入力を生成する。
-     * @param name リソース名
-     * @return リソースからの入力
-     */
-    public static InputStream getResourceAsStream(String name){
-        return SELF_KLASS.getResourceAsStream(name);
-    }
-
-    /**
-     * クラスローダを介してリソース読み込み用URLを生成する。
-     * @param name リソース名
-     * @return URL
-     */
-    public static URL getResource(String name){
-        return SELF_KLASS.getResource(name);
-    }
-
-    /**
-     * 共通ロガーを取得する。
-     * @return 共通ロガー
-     */
-    public static LogWrapper logger(){
-        return COMMON_LOGGER;
-    }
-
-    /**
-     * VMごとプログラムを終了する。
-     * ※おそらく随所でシャットダウンフックが起動されるはず。
-     *
-     * @param exitCode 終了コード
-     * @throws java.lang.SecurityException セキュリティ違反
-     */
-    public static void exit(int exitCode) throws SecurityException{
-        logger().info(
-                "終了コード["
-                + exitCode
-                + "]でVMごとアプリケーションを終了します。" );
-        RUNTIME.runFinalization();
-        System.out.flush();
-        System.err.flush();
-        try{
-            RUNTIME.exit(exitCode);
-        }catch(SecurityException e){
-            logger().warn(
-                     "セキュリティ設定により、"
-                    +"VMを終了させることができません。", e);
-            throw e;
-        }
-        return;
-    }
-
-    /**
-     * Jindolf のスタートアップエントリ。
-     *
-     * @param args コマンドライン引数
-     */
-    public static void main(final String[] args){
-        // VM内二重起動チェック
-        boolean hasInvoked = ! INVOKE_FLAG.compareAndSet(false, true);
-        if(hasInvoked){
-            String errmsg = "二度目以降の起動がキャンセルされました。";
-            errorDialog("多重起動", errmsg);
-
-            // exitせずに戻るのみ
-            return;
-        }
-
-        checkGUIEnvironment();
-
-        // ここからGUIウィンドウとマウス解禁
-
-        checkCompileError();
-        checkPackageDefinition();
-
-        try{
-            option = OptionInfo.parseOptions(args);
-        }catch(IllegalArgumentException e){
-            String message = e.getLocalizedMessage();
-            System.err.println(message);
-            System.err.println(
-                "起動オプション一覧は、"
-                + "起動オプションに「"
-                + CmdOption.OPT_HELP.toHyphened()
-                + "」を指定すると確認できます。" );
-            Jindolf.RUNTIME.exit(1);
-            assert false;
-            return;
-        }
-
-        if(option.hasOption(CmdOption.OPT_HELP)){
-            showHelpMessage();
-            RUNTIME.exit(0);
-            return;
-        }
-
-        if(option.hasOption(CmdOption.OPT_VERSION)){
-            System.out.println(ID);
-            RUNTIME.exit(0);
-            return;
-        }
-
-        // あらゆるSwingコンポーネント操作より前に必要。
-        if(option.hasOption(CmdOption.OPT_BOLDMETAL)){
-            // もの凄く日本語表示が汚くなるかもよ!注意
-            UIManager.put("swing.boldMetal", Boolean.TRUE);
-        }else{
-            UIManager.put("swing.boldMetal", Boolean.FALSE);
-        }
-
-        // JRE1.5用スプラッシュウィンドウ
-        Window splashWindow = null;
-        if(   ! JRE_PACKAGE.isCompatibleWith("1.6")
-           && ! option.hasOption(CmdOption.OPT_NOSPLASH) ){
-            splashWindow = createSplashWindow();
-            splashWindow.setVisible(true);
-            Thread.yield();
-        }
-
-        setting = new AppSetting();
-        setting.applyOptionInfo(option);
-
-        if(option.hasOption(CmdOption.OPT_VMINFO)){
-            System.out.println(EnvInfo.getVMInfo());
-        }
-
-        initLogging(option.hasOption(CmdOption.OPT_CONSOLELOG));
-        // ここからロギング解禁
-        // Jindolf.exit()もここから解禁
-
-        dumpBootInfo();
-
-        ConfigFile.setupConfigDirectory();
-        ConfigFile.setupLockFile();
-        // ここから設定格納ディレクトリ解禁
-
-        setting.loadConfig();
-
-        RUNTIME.addShutdownHook(new Thread(){
-            @Override
-            public void run(){
-                logger().info("シャットダウン処理に入ります…");
-                System.out.flush();
-                System.err.flush();
-                RUNTIME.gc();
-                Thread.yield();
-                RUNTIME.runFinalization(); // 危険?
-                Thread.yield();
-                return;
-            }
-        });
-
-        preInitClass();
-
-        GUIUtils.replaceEventQueue();
-
-        boolean hasError = false;
-        try{
-            EventQueue.invokeAndWait(new Runnable(){
-                public void run(){
-                    startGUI();
-                    return;
-                }
-            });
-        }catch(InvocationTargetException e){
-            logger().fatal("アプリケーション初期化に失敗しました", e);
-            e.printStackTrace(System.err);
-            hasError = true;
-        }catch(InterruptedException e){
-            logger().fatal("アプリケーション初期化に失敗しました", e);
-            e.printStackTrace(System.err);
-            hasError = true;
-        }finally{
-            if(splashWindow != null){
-                splashWindow.setVisible(false);
-                splashWindow.dispose();
-                splashWindow = null;
-            }
-        }
-
-        if(hasError) exit(1);
-
-        return;
-    }
-
-}
diff --git a/src/main/java/jp/sourceforge/jindolf/OptionInfo.java b/src/main/java/jp/sourceforge/jindolf/OptionInfo.java
deleted file mode 100644 (file)
index 1d9c6b3..0000000
+++ /dev/null
@@ -1,295 +0,0 @@
-/*
- * option argument information
- *
- * License : The MIT License
- * Copyright(c) 2009 olyutorskii
- */
-
-package jp.sourceforge.jindolf;
-
-import java.util.Collections;
-import java.util.EnumMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * コマンドラインオプション情報。
- * public static void main()の引数から展開される。
- */
-public class OptionInfo{
-
-    private static final Pattern PATTERN_GEOMETRY =
-            Pattern.compile(
-                 "([1-9][0-9]*)x([1-9][0-9]*)"
-                +"(?:(\\+|\\-)([1-9][0-9]*)(\\+|\\-)([1-9][0-9]*))?"
-                );
-
-
-    private Integer frameWidth  = null;
-    private Integer frameHeight = null;
-    private Integer frameXpos = null;
-    private Integer frameYpos = null;
-
-    private final List<String> invokeArgs = new LinkedList<String>();
-    private final List<CmdOption> optionList = new LinkedList<CmdOption>();
-    private final Map<CmdOption, Boolean> boolOptionMap =
-            new EnumMap<CmdOption, Boolean>(CmdOption.class);
-    private final Map<CmdOption, String> stringOptionMap =
-            new EnumMap<CmdOption, String>(CmdOption.class);
-
-
-    /**
-     * コンストラクタ。
-     */
-    protected OptionInfo(){
-        super();
-        return;
-    }
-
-
-    /**
-     * オプション文字列を解析する。
-     * @param args main()に渡されるオプション文字列
-     * @return 解析済みのオプション情報。
-     * @throws IllegalArgumentException 構文エラー
-     */
-    public static OptionInfo parseOptions(String[] args)
-            throws IllegalArgumentException{
-        OptionInfo result = new OptionInfo();
-
-        result.invokeArgs.clear();
-        for(String arg : args){
-            if(arg == null) continue;
-            if(arg.length() <= 0) continue;
-            result.invokeArgs.add(arg);
-        }
-        Iterator<String> iterator = result.invokeArgs.iterator();
-
-        while(iterator.hasNext()){
-            String arg = iterator.next();
-
-            CmdOption option = CmdOption.parseCmdOption(arg);
-            if(option == null){
-                throw new IllegalArgumentException(
-                        "未定義の起動オプション["
-                        + arg
-                        + "]が指定されました。");
-            }
-            result.optionList.add(option);
-
-            if(CmdOption.isIndepOption(option)){
-                continue;
-            }else if(CmdOption.isBooleanOption(option)){
-                Boolean bool = parseBooleanSwitch(arg, iterator);
-                result.boolOptionMap.put(option, bool);
-                continue;
-            }
-
-            switch(option){
-            case OPT_INITFONT:
-            case OPT_CONFDIR:
-                checkNextArg(arg, iterator);
-                result.stringOptionMap.put(option, iterator.next());
-                break;
-            case OPT_GEOMETRY:
-                checkNextArg(arg, iterator);
-                String geometry = iterator.next();
-                Matcher matcher = PATTERN_GEOMETRY.matcher(geometry);
-                if( ! matcher.matches() ){
-                    throw new IllegalArgumentException(
-                            "起動オプション["
-                            +arg
-                            +"]の引数形式["
-                            +geometry
-                            +"]が不正です。" );
-                }
-                String width  = matcher.group(1);
-                String height = matcher.group(2);
-                String xSign  = matcher.group(3);
-                String xPos   = matcher.group(4);
-                String ySign  = matcher.group(5);
-                String yPos   = matcher.group(6);
-                try{
-                    result.frameWidth  = Integer.parseInt(width);
-                    result.frameHeight = Integer.parseInt(height);
-                    if(xPos != null && xPos.length() > 0){
-                        result.frameXpos = Integer.parseInt(xPos);
-                        if(xSign.equals("-")){
-                            result.frameXpos = -result.frameXpos;
-                        }
-                    }
-                    if(yPos != null && yPos.length() > 0){
-                        result.frameYpos = Integer.parseInt(yPos);
-                        if(ySign.equals("-")){
-                            result.frameYpos = -result.frameYpos;
-                        }
-                    }
-                }catch(NumberFormatException e){
-                    assert false;
-                    throw new IllegalArgumentException(e);
-                }
-
-                break;
-            default:
-                assert false;
-                break;
-            }
-        }
-
-        return result;
-    }
-
-    /**
-     * 真偽二値をとるオプション解析の下請け。
-     * @param option オプション名
-     * @param iterator 引数並び
-     * @return 真偽
-     * @throws IllegalArgumentException 構文エラー
-     */
-    private static Boolean parseBooleanSwitch(
-            String option, Iterator<String> iterator )
-                throws IllegalArgumentException{
-        Boolean result;
-        checkNextArg(option, iterator);
-        String onoff = iterator.next();
-        if(   onoff.compareToIgnoreCase("on"  ) == 0
-           || onoff.compareToIgnoreCase("yes" ) == 0
-           || onoff.compareToIgnoreCase("true") == 0){
-            result = Boolean.TRUE;
-        }else if(   onoff.compareToIgnoreCase("off"  ) == 0
-                 || onoff.compareToIgnoreCase("no"   ) == 0
-                 || onoff.compareToIgnoreCase("false") == 0){
-            result = Boolean.FALSE;
-        }else{
-            throw new IllegalArgumentException(
-                    "起動オプション["
-                    +option
-                    +"]の引数形式["
-                    +onoff
-                    +"]が不正です。"
-                    +"on, off, yes, no, true, false"
-                    +"のいずれかを指定してください。");
-        }
-        return result;
-    }
-
-    /**
-     * 追加引数を持つオプションのチェック。
-     * @param option オプション名
-     * @param iterator 引数並び
-     * @throws IllegalArgumentException 構文エラー
-     */
-    private static void checkNextArg(CharSequence option,
-                                       Iterator<String> iterator )
-                                       throws IllegalArgumentException{
-        if( ! iterator.hasNext() ){
-            throw new IllegalArgumentException(
-                    "起動オプション["
-                    +option
-                    +"]に引数がありません。");
-        }
-        return;
-    }
-
-
-    /**
-     * 全引数のリストを返す。
-     * @return 全引数のリスト
-     */
-    public List<String> getInvokeArgList(){
-        return Collections.unmodifiableList(this.invokeArgs);
-    }
-
-    /**
-     * オプションが指定されていたか否か判定する。
-     * @param option オプション
-     * @return 指定されていたらtrue
-     */
-    public boolean hasOption(CmdOption option){
-        if(this.optionList.contains(option)) return true;
-        return false;
-    }
-
-    /**
-     * 真偽値をとるオプション値を返す。
-     * 複数回指定された場合は最後の値。
-     * @param option オプション
-     * @return 真偽値。オプション指定がなかった場合はnull
-     * @throws IllegalArgumentException 真偽値を取るオプションではない。
-     */
-    public Boolean getBooleanArg(CmdOption option)
-            throws IllegalArgumentException{
-        if( ! CmdOption.isBooleanOption(option) ){
-            throw new IllegalArgumentException();
-        }
-        Boolean result = this.boolOptionMap.get(option);
-        return result;
-    }
-
-    /**
-     * 文字列引数をとるオプション値を返す。
-     * 複数回指定された場合は最後の値。
-     * @param option オプション
-     * @return 文字列。オプション指定がなかった場合はnull
-     */
-    public String getStringArg(CmdOption option){
-        String result = this.stringOptionMap.get(option);
-        return result;
-    }
-
-    /**
-     * 排他的オプションのいずれかが指定されたか判定する。
-     * 後から指定された方が有効となる。
-     * @param options 排他的オプション群
-     * @return いずれかのオプション。どれも指定されなければnull
-     */
-    public CmdOption getExclusiveOption(CmdOption... options){
-        CmdOption result = null;
-        for(CmdOption option : this.optionList){
-            for(CmdOption excOption : options){
-                if(option == excOption){
-                    result = option;
-                    break;
-                }
-            }
-        }
-        return result;
-    }
-
-    /**
-     * 初期のフレーム幅を返す。
-     * @return 初期のフレーム幅。オプション指定されてなければnull
-     */
-    public Integer initialFrameWidth(){
-        return this.frameWidth;
-    }
-
-    /**
-     * 初期のフレーム高を返す。
-     * @return 初期のフレーム高。オプション指定されてなければnull
-     */
-    public Integer initialFrameHeight(){
-        return this.frameHeight;
-    }
-
-    /**
-     * 初期のフレーム位置のX座標を返す。
-     * @return 初期のフレーム位置のX座標。オプション指定されてなければnull
-     */
-    public Integer initialFrameXpos(){
-        return this.frameXpos;
-    }
-
-    /**
-     * 初期のフレーム位置のY座標を返す。
-     * @return 初期のフレーム位置のY座標。オプション指定されてなければnull
-     */
-    public Integer initialFrameYpos(){
-        return this.frameYpos;
-    }
-
-}
diff --git a/src/main/resources/jp/sfjp/jindolf/resources/version.properties b/src/main/resources/jp/sfjp/jindolf/resources/version.properties
new file mode 100644 (file)
index 0000000..7f74c02
--- /dev/null
@@ -0,0 +1,12 @@
+# Version definition
+#   [ with Maven resource filtering ]
+
+pkg-title.jp.sfjp.jindolf = ${pom.name}
+pkg-version.jp.sfjp.jindolf = ${pom.version}
+pkg-author.jp.sfjp.jindolf = olyutorskii
+pkg-license.jp.sfjp.jindolf = The MIT License
+pkg-contact.jp.sfjp.jindolf = ${pom.url}
+pkg-inception.jp.sfjp.jindolf = ${pom.inceptionYear}
+pkg-comment.jp.sfjp.jindolf =
+
+# EOF #
diff --git a/src/main/resources/jp/sourceforge/jindolf/resources/version.properties b/src/main/resources/jp/sourceforge/jindolf/resources/version.properties
deleted file mode 100644 (file)
index efae2b4..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-# Version definition
-#   [ with Maven resource filtering ]
-
-pkg-title.jp.sourceforge.jindolf = ${pom.name}
-pkg-version.jp.sourceforge.jindolf = ${pom.version}
-pkg-author.jp.sourceforge.jindolf = olyutorskii
-pkg-license.jp.sourceforge.jindolf = The MIT License
-pkg-contact.jp.sourceforge.jindolf = ${pom.url}
-pkg-debut.jp.sourceforge.jindolf = ${pom.inceptionYear}
-pkg-comment.jp.sourceforge.jindolf =
-
-# EOF #
diff --git a/src/test/java/jp/sfjp/jindolf/config/CmdOptionTest.java b/src/test/java/jp/sfjp/jindolf/config/CmdOptionTest.java
new file mode 100644 (file)
index 0000000..6a87a14
--- /dev/null
@@ -0,0 +1,237 @@
+/*
+ * CmdOption test
+ *
+ * Copyright 2012 olyutorskii
+ */
+
+package jp.sfjp.jindolf.config;
+
+import java.util.List;
+import java.util.Arrays;
+import java.util.LinkedList;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ */
+public class CmdOptionTest {
+
+    public CmdOptionTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception {
+    }
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    /**
+     * Test of values method, of class CmdOption.
+     */
+    @Test
+    public void testValues() {
+        System.out.println("values");
+
+        CmdOption[] values = CmdOption.values();
+
+        assertEquals(12, values.length);
+
+        List<CmdOption> list = Arrays.asList(values);
+
+        List<CmdOption> testList = new LinkedList<CmdOption>();
+        testList.add(CmdOption.OPT_HELP);
+        testList.add(CmdOption.OPT_VERSION);
+        testList.add(CmdOption.OPT_BOLDMETAL);
+        testList.add(CmdOption.OPT_NOSPLASH);
+        testList.add(CmdOption.OPT_GEOMETRY);
+        testList.add(CmdOption.OPT_VMINFO);
+        testList.add(CmdOption.OPT_CONSOLELOG);
+        testList.add(CmdOption.OPT_INITFONT);
+        testList.add(CmdOption.OPT_ANTIALIAS);
+        testList.add(CmdOption.OPT_FRACTIONAL);
+        testList.add(CmdOption.OPT_CONFDIR);
+        testList.add(CmdOption.OPT_NOCONF);
+
+        assertTrue(list.containsAll(testList));
+        assertTrue(testList.containsAll(list));
+        assertEquals(testList.size(), list.size());
+
+        return;
+    }
+
+    /**
+     * Test of valueOf method, of class CmdOption.
+     */
+    @Test
+    public void testValueOf() {
+        System.out.println("valueOf");
+
+        CmdOption expResult;
+        CmdOption result;
+
+        expResult = CmdOption.OPT_HELP;
+        result = CmdOption.valueOf("OPT_HELP");
+        assertEquals(expResult, result);
+
+        try{
+            CmdOption.valueOf("X");
+            fail();
+        }catch(IllegalArgumentException e){
+            // GOOD
+        }
+
+        return;
+    }
+
+    /**
+     * Test of getHelpText method, of class CmdOption.
+     */
+    @Test
+    public void testGetHelpText() {
+        System.out.println("getHelpText");
+
+        CharSequence result = CmdOption.getHelpText();
+
+        assertNotNull(result);
+        assertTrue(result.length() > 0);
+        assertTrue(result.toString().endsWith("\n"));
+
+        return;
+    }
+
+    /**
+     * Test of parseCmdOption method, of class CmdOption.
+     */
+    @Test
+    public void testParseCmdOption() {
+        System.out.println("parseCmdOption");
+
+        assertNull(CmdOption.parseCmdOption(""));
+        assertNull(CmdOption.parseCmdOption("X"));
+
+        assertEquals(CmdOption.OPT_HELP, CmdOption.parseCmdOption("-help"));
+        assertEquals(CmdOption.OPT_HELP, CmdOption.parseCmdOption("-?"));
+
+        assertEquals(CmdOption.OPT_NOCONF,
+                     CmdOption.parseCmdOption("-noconfdir"));
+
+        return;
+    }
+
+    /**
+     * Test of matches method, of class CmdOption.
+     */
+    @Test
+    public void testMatches() {
+        System.out.println("matches");
+
+        assertFalse(CmdOption.OPT_HELP.matches(""));
+        assertFalse(CmdOption.OPT_HELP.matches("help"));
+
+        assertTrue(CmdOption.OPT_HELP.matches("-help"));
+        assertTrue(CmdOption.OPT_HELP.matches("-h"));
+        assertTrue(CmdOption.OPT_HELP.matches("--help"));
+        assertTrue(CmdOption.OPT_HELP.matches("-?"));
+
+        assertTrue(CmdOption.OPT_VERSION.matches("-version"));
+        assertTrue(CmdOption.OPT_BOLDMETAL.matches("-boldMetal"));
+        assertTrue(CmdOption.OPT_NOSPLASH.matches("-nosplash"));
+        assertTrue(CmdOption.OPT_GEOMETRY.matches("-geometry"));
+        assertTrue(CmdOption.OPT_VMINFO.matches("-vminfo"));
+        assertTrue(CmdOption.OPT_CONSOLELOG.matches("-consolelog"));
+        assertTrue(CmdOption.OPT_INITFONT.matches("-initfont"));
+        assertTrue(CmdOption.OPT_ANTIALIAS.matches("-antialias"));
+        assertTrue(CmdOption.OPT_FRACTIONAL.matches("-fractional"));
+        assertTrue(CmdOption.OPT_CONFDIR.matches("-confdir"));
+        assertTrue(CmdOption.OPT_NOCONF.matches("-noconfdir"));
+
+        return;
+    }
+
+    /**
+     * Test of isIndepOption method, of class CmdOption.
+     */
+    @Test
+    public void testIsIndepOption() {
+        System.out.println("isIndepOption");
+
+        for(CmdOption opt : CmdOption.values()){
+            switch(opt){
+            case OPT_HELP:
+            case OPT_VERSION:
+            case OPT_VMINFO:
+            case OPT_BOLDMETAL:
+            case OPT_NOSPLASH:
+            case OPT_CONSOLELOG:
+            case OPT_NOCONF:
+                assertTrue(opt.isIndepOption());
+                break;
+            default:
+                assertFalse(opt.isIndepOption());
+                break;
+            }
+        }
+
+        return;
+    }
+
+    /**
+     * Test of isBooleanOption method, of class CmdOption.
+     */
+    @Test
+    public void testIsBooleanOption() {
+        System.out.println("isBooleanOption");
+
+        for(CmdOption opt : CmdOption.values()){
+            switch(opt){
+            case OPT_ANTIALIAS:
+            case OPT_FRACTIONAL:
+                assertTrue(opt.isBooleanOption());
+                break;
+            default:
+                assertFalse(opt.isBooleanOption());
+                break;
+            }
+        }
+
+        return;
+    }
+
+    /**
+     * Test of toString method, of class CmdOption.
+     */
+    @Test
+    public void testToString() {
+        System.out.println("toString");
+
+        assertEquals("-help", CmdOption.OPT_HELP.toString());
+        assertEquals("-version", CmdOption.OPT_VERSION.toString());
+        assertEquals("-boldMetal", CmdOption.OPT_BOLDMETAL.toString());
+        assertEquals("-nosplash", CmdOption.OPT_NOSPLASH.toString());
+        assertEquals("-geometry", CmdOption.OPT_GEOMETRY.toString());
+        assertEquals("-vminfo", CmdOption.OPT_VMINFO.toString());
+        assertEquals("-consolelog", CmdOption.OPT_CONSOLELOG.toString());
+        assertEquals("-initfont", CmdOption.OPT_INITFONT.toString());
+        assertEquals("-antialias", CmdOption.OPT_ANTIALIAS.toString());
+        assertEquals("-fractional", CmdOption.OPT_FRACTIONAL.toString());
+        assertEquals("-confdir", CmdOption.OPT_CONFDIR.toString());
+        assertEquals("-noconfdir", CmdOption.OPT_NOCONF.toString());
+
+        return;
+    }
+}
diff --git a/src/test/java/jp/sfjp/jindolf/config/OptionInfoTest.java b/src/test/java/jp/sfjp/jindolf/config/OptionInfoTest.java
new file mode 100644 (file)
index 0000000..0fbff02
--- /dev/null
@@ -0,0 +1,378 @@
+/*
+ * OptionInfo test
+ *
+ * Copyright 2012 olyutorskii
+ */
+
+package jp.sfjp.jindolf.config;
+
+import java.util.List;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ */
+public class OptionInfoTest {
+
+    public OptionInfoTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception {
+    }
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    /**
+     * Test of parseOptions method, of class OptionInfo.
+     */
+    @Test
+    public void testParseOptions() {
+        System.out.println("parseOptions");
+
+        OptionInfo result;
+
+        result = OptionInfo.parseOptions();
+        assertNotNull(result);
+
+        result = OptionInfo.parseOptions(null, null);
+        assertNotNull(result);
+        assertTrue(result.getInvokeArgList().isEmpty());
+
+        result = OptionInfo.parseOptions(new String[]{});
+        assertNotNull(result);
+
+        result = OptionInfo.parseOptions("-help");
+        assertNotNull(result);
+
+        try{
+            result = OptionInfo.parseOptions("X");
+            fail();
+        }catch(IllegalArgumentException e){
+            assertEquals("未定義の起動オプション[X]が指定されました。",
+                         e.getMessage() );
+        }
+
+        try{
+            result = OptionInfo.parseOptions("");
+            fail();
+        }catch(IllegalArgumentException e){
+            assertEquals("未定義の起動オプション[]が指定されました。",
+                         e.getMessage() );
+        }
+
+        return;
+    }
+
+    /**
+     * Test of getInvokeArgList method, of class OptionInfo.
+     */
+    @Test
+    public void testGetInvokeArgList() {
+        System.out.println("getInvokeArgList");
+
+        OptionInfo result;
+        List<String> list;
+
+        result = OptionInfo.parseOptions();
+        list = result.getInvokeArgList();
+        assertTrue(list.isEmpty());
+
+        result = OptionInfo.parseOptions("-help");
+        list = result.getInvokeArgList();
+        assertEquals(1, list.size());
+        assertEquals("-help", list.get(0));
+
+        result = OptionInfo.parseOptions("-initfont", "on", "-help");
+        list = result.getInvokeArgList();
+        assertEquals(3, list.size());
+        assertEquals("-initfont", list.get(0));
+        assertEquals("on", list.get(1));
+        assertEquals("-help", list.get(2));
+
+        return;
+    }
+
+    /**
+     * Test of hasOption method, of class OptionInfo.
+     */
+    @Test
+    public void testHasOption() {
+        System.out.println("hasOption");
+
+        OptionInfo result;
+
+        result = OptionInfo.parseOptions();
+        assertFalse(result.hasOption(CmdOption.OPT_HELP));
+        assertFalse(result.hasOption(CmdOption.OPT_INITFONT));
+
+        result = OptionInfo.parseOptions("-help");
+        assertTrue(result.hasOption(CmdOption.OPT_HELP));
+        assertFalse(result.hasOption(CmdOption.OPT_INITFONT));
+
+        result = OptionInfo.parseOptions("-initfont", "on", "-help");
+        assertTrue(result.hasOption(CmdOption.OPT_HELP));
+        assertTrue(result.hasOption(CmdOption.OPT_INITFONT));
+
+        result = OptionInfo.parseOptions("-initfont", "off", "-help");
+        assertTrue(result.hasOption(CmdOption.OPT_HELP));
+        assertTrue(result.hasOption(CmdOption.OPT_INITFONT));
+
+        return;
+    }
+
+    /**
+     * Test of getBooleanArg method, of class OptionInfo.
+     */
+    @Test
+    public void testGetBooleanArg() {
+        System.out.println("getBooleanArg");
+
+        OptionInfo result;
+
+        try{
+            result = OptionInfo.parseOptions("-antialias");
+            fail();
+        }catch(IllegalArgumentException e){
+            String expMsg =
+                    "起動オプション[-antialias]に引数がありません。";
+            assertEquals(expMsg, e.getMessage());
+        }
+
+        try{
+            result = OptionInfo.parseOptions("-fractional");
+            fail();
+        }catch(IllegalArgumentException e){
+            String expMsg =
+                    "起動オプション[-fractional]に引数がありません。";
+            assertEquals(expMsg, e.getMessage());
+        }
+
+        result = OptionInfo.parseOptions("-help");
+        try{
+            result.getBooleanArg(CmdOption.OPT_HELP);
+            fail();
+        }catch(IllegalArgumentException e){
+            String expMsg =
+                      "起動オプション[-help]は"
+                    + "真偽を指定するオプションではありません。";
+            assertEquals(expMsg, e.getMessage());
+        }
+
+        result = OptionInfo.parseOptions();
+        assertNull(result.getBooleanArg(CmdOption.OPT_ANTIALIAS));
+        assertNull(result.getBooleanArg(CmdOption.OPT_FRACTIONAL));
+
+
+        String[] flags;
+
+        flags = new String[]{"on", "yes", "true", "ON", "YES", "TRUE"};
+        for(String flag : flags){
+            result = OptionInfo.parseOptions("-antialias", flag);
+            assertTrue(result.getBooleanArg(CmdOption.OPT_ANTIALIAS));
+            result = OptionInfo.parseOptions("-fractional", flag);
+            assertTrue(result.getBooleanArg(CmdOption.OPT_FRACTIONAL));
+        }
+
+        flags = new String[]{"off", "no", "false", "OFF", "NO", "FALSE"};
+        for(String flag : flags){
+            result = OptionInfo.parseOptions("-antialias", flag);
+            assertFalse(result.getBooleanArg(CmdOption.OPT_ANTIALIAS));
+            result = OptionInfo.parseOptions("-fractional", flag);
+            assertFalse(result.getBooleanArg(CmdOption.OPT_FRACTIONAL));
+        }
+
+        try{
+            result = OptionInfo.parseOptions("-antialias", "X");
+            fail();
+        }catch(IllegalArgumentException e){
+            String expMsg =
+                    "起動オプション[-antialias]の真偽指定[X]が不正です。"
+                    + "on, off, yes, no, true, false"
+                    + "のいずれかを指定してください。";
+            assertEquals(expMsg, e.getMessage());
+        }
+
+        try{
+            result = OptionInfo.parseOptions("-fractional", "X");
+            fail();
+        }catch(IllegalArgumentException e){
+            String expMsg =
+                    "起動オプション[-fractional]の真偽指定[X]が不正です。"
+                    + "on, off, yes, no, true, false"
+                    + "のいずれかを指定してください。";
+            assertEquals(expMsg, e.getMessage());
+        }
+
+        result = OptionInfo.parseOptions("-antialias", "on",
+                                         "-antialias", "off" );
+        assertFalse(result.getBooleanArg(CmdOption.OPT_ANTIALIAS));
+
+        return;
+    }
+
+    /**
+     * Test of getStringArg method, of class OptionInfo.
+     */
+    @Test
+    public void testGetStringArg() {
+        System.out.println("getStringArg");
+
+        OptionInfo result;
+
+        try{
+            result = OptionInfo.parseOptions("-initfont");
+            fail();
+        }catch(IllegalArgumentException e){
+            String expMsg =
+                    "起動オプション[-initfont]に引数がありません。";
+            assertEquals(expMsg, e.getMessage());
+        }
+
+        try{
+            result = OptionInfo.parseOptions("-confdir");
+            fail();
+        }catch(IllegalArgumentException e){
+            String expMsg =
+                    "起動オプション[-confdir]に引数がありません。";
+            assertEquals(expMsg, e.getMessage());
+        }
+
+        result = OptionInfo.parseOptions();
+        assertNull(result.getStringArg(CmdOption.OPT_INITFONT));
+        assertNull(result.getStringArg(CmdOption.OPT_CONFDIR));
+
+        result = OptionInfo.parseOptions("-initfont", "Monospaced-PLAIN-16");
+        assertEquals("Monospaced-PLAIN-16",
+                     result.getStringArg(CmdOption.OPT_INITFONT));
+
+        result = OptionInfo.parseOptions("-confdir", "/tmp/x");
+        assertEquals("/tmp/x",
+                     result.getStringArg(CmdOption.OPT_CONFDIR));
+
+        result = OptionInfo.parseOptions("-confdir", "/tmp/x",
+                                         "-confdir", "/tmp/y" );
+        assertEquals("/tmp/y",
+                     result.getStringArg(CmdOption.OPT_CONFDIR));
+
+        return;
+    }
+
+    /**
+     * Test of getExclusiveOption method, of class OptionInfo.
+     */
+    @Test
+    public void testGetExclusiveOption() {
+        System.out.println("getExclusiveOption");
+
+        OptionInfo result;
+        CmdOption opt;
+
+        result = OptionInfo.parseOptions();
+        opt = result.getExclusiveOption(CmdOption.OPT_CONFDIR,
+                                        CmdOption.OPT_NOCONF );
+        assertNull(opt);
+
+        result = OptionInfo.parseOptions("-help");
+        opt = result.getExclusiveOption(CmdOption.OPT_CONFDIR,
+                                        CmdOption.OPT_NOCONF );
+        assertNull(opt);
+
+        result = OptionInfo.parseOptions("-confdir", "/tmp/x");
+        opt = result.getExclusiveOption(CmdOption.OPT_CONFDIR,
+                                        CmdOption.OPT_NOCONF );
+        assertSame(CmdOption.OPT_CONFDIR, opt);
+
+        result = OptionInfo.parseOptions("-noconfdir");
+        opt = result.getExclusiveOption(CmdOption.OPT_CONFDIR,
+                                        CmdOption.OPT_NOCONF );
+        assertSame(CmdOption.OPT_NOCONF, opt);
+
+        result = OptionInfo.parseOptions("-confdir", "/tmp/x",
+                                         "-noconfdir" );
+        opt = result.getExclusiveOption(CmdOption.OPT_CONFDIR,
+                                        CmdOption.OPT_NOCONF );
+        assertSame(CmdOption.OPT_NOCONF, opt);
+
+        result = OptionInfo.parseOptions("-noconfdir",
+                                         "-confdir", "/tmp/x" );
+        opt = result.getExclusiveOption(CmdOption.OPT_CONFDIR,
+                                        CmdOption.OPT_NOCONF );
+        assertSame(CmdOption.OPT_CONFDIR, opt);
+
+        return;
+    }
+
+    /**
+     * Test of geometry option, of class OptionInfo.
+     */
+    @Test
+    public void testGeometry() {
+        System.out.println("initialFrameWidth");
+
+        OptionInfo result;
+
+        result = OptionInfo.parseOptions();
+        assertNull(result.initialFrameWidth());
+        assertNull(result.initialFrameHeight());
+        assertNull(result.initialFrameXpos());
+        assertNull(result.initialFrameYpos());
+
+        try{
+            result = OptionInfo.parseOptions("-geometry");
+            fail();
+        }catch(IllegalArgumentException e){
+            String expMsg =
+                    "起動オプション[-geometry]に引数がありません。";
+            assertEquals(expMsg, e.getMessage());
+        }
+
+        try{
+            result = OptionInfo.parseOptions("-geometry", "Q");
+            fail();
+        }catch(IllegalArgumentException e){
+            String expMsg =
+                      "起動オプション[-geometry]の"
+                    + "ジオメトリ指定[Q]が不正です。"
+                    + "WIDTHxHEIGHT[(+|-)XPOS(+|-)YPOS]"
+                    + "の形式で指定してください";
+            assertEquals(expMsg, e.getMessage());
+        }
+
+        result = OptionInfo.parseOptions("-geometry", "800x600");
+        assertEquals(800, result.initialFrameWidth().intValue());
+        assertEquals(600, result.initialFrameHeight().intValue());
+        assertNull(result.initialFrameXpos());
+        assertNull(result.initialFrameYpos());
+
+        result = OptionInfo.parseOptions("-geometry", "800x600+100+200");
+        assertEquals(800, result.initialFrameWidth().intValue());
+        assertEquals(600, result.initialFrameHeight().intValue());
+        assertEquals(100, result.initialFrameXpos().intValue());
+        assertEquals(200, result.initialFrameYpos().intValue());
+
+        result = OptionInfo.parseOptions("-geometry", "800x600-100-200");
+        assertEquals(800, result.initialFrameWidth().intValue());
+        assertEquals(600, result.initialFrameHeight().intValue());
+        assertEquals(-100, result.initialFrameXpos().intValue());
+        assertEquals(-200, result.initialFrameYpos().intValue());
+
+        return;
+    }
+
+}
@@ -4,7 +4,7 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.data;
 
 import java.util.List;
 import java.util.regex.Matcher;
@@ -4,7 +4,7 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.net;
 
 import org.junit.After;
 import org.junit.AfterClass;
@@ -4,7 +4,7 @@
  * Copyright(c) 2009 olyutorskii
  */
 
-package jp.sourceforge.jindolf;
+package jp.sfjp.jindolf.util;
 
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;