OSDN Git Service

スタートアップ処理の改善
authorOlyutorskii <olyutorskii@users.osdn.me>
Wed, 7 Mar 2012 22:54:36 +0000 (07:54 +0900)
committerOlyutorskii <olyutorskii@users.osdn.me>
Wed, 7 Mar 2012 22:54:36 +0000 (07:54 +0900)
49 files changed:
CHANGELOG.txt
pom.xml
src/main/java/jp/sfjp/jindolf/Controller.java
src/main/java/jp/sfjp/jindolf/Jindolf.java
src/main/java/jp/sfjp/jindolf/JindolfGuiJp.java [deleted file]
src/main/java/jp/sfjp/jindolf/JindolfJre15.java
src/main/java/jp/sfjp/jindolf/JindolfMain.java [moved from src/main/java/jp/sfjp/jindolf/JindolfOld.java with 50% similarity]
src/main/java/jp/sfjp/jindolf/JreChecker.java
src/main/java/jp/sfjp/jindolf/ResourceManager.java
src/main/java/jp/sfjp/jindolf/VerInfo.java
src/main/java/jp/sfjp/jindolf/config/AppSetting.java
src/main/java/jp/sfjp/jindolf/config/CmdOption.java
src/main/java/jp/sfjp/jindolf/config/ConfigFile.java
src/main/java/jp/sfjp/jindolf/config/ConfigStore.java
src/main/java/jp/sfjp/jindolf/config/FileUtils.java
src/main/java/jp/sfjp/jindolf/config/InterVMLock.java
src/main/java/jp/sfjp/jindolf/config/OptionInfo.java
src/main/java/jp/sfjp/jindolf/data/Land.java
src/main/java/jp/sfjp/jindolf/data/LandsModel.java
src/main/java/jp/sfjp/jindolf/data/Period.java
src/main/java/jp/sfjp/jindolf/data/Village.java
src/main/java/jp/sfjp/jindolf/data/package-info.java [new file with mode: 0644]
src/main/java/jp/sfjp/jindolf/dxchg/TextPopup.java
src/main/java/jp/sfjp/jindolf/dxchg/WebIPCDialog.java
src/main/java/jp/sfjp/jindolf/dxchg/WolfBBS.java
src/main/java/jp/sfjp/jindolf/glyph/Font2Json.java [new file with mode: 0644]
src/main/java/jp/sfjp/jindolf/glyph/FontChooser.java
src/main/java/jp/sfjp/jindolf/glyph/FontEnv.java [new file with mode: 0644]
src/main/java/jp/sfjp/jindolf/glyph/FontInfo.java
src/main/java/jp/sfjp/jindolf/glyph/FontListModel.java
src/main/java/jp/sfjp/jindolf/glyph/FontUtils.java [deleted file]
src/main/java/jp/sfjp/jindolf/log/LogUtils.java
src/main/java/jp/sfjp/jindolf/log/LogWrapper.java [deleted file]
src/main/java/jp/sfjp/jindolf/log/LoggingDispatcher.java
src/main/java/jp/sfjp/jindolf/log/MomentaryHandler.java [moved from src/main/java/jp/sfjp/jindolf/log/PileHandler.java with 50% similarity]
src/main/java/jp/sfjp/jindolf/log/SwingDocHandler.java
src/main/java/jp/sfjp/jindolf/net/HttpUtils.java
src/main/java/jp/sfjp/jindolf/net/ServerAccess.java
src/main/java/jp/sfjp/jindolf/net/TallyInputStream.java
src/main/java/jp/sfjp/jindolf/net/TallyOutputStream.java
src/main/java/jp/sfjp/jindolf/summary/VillageDigest.java
src/main/java/jp/sfjp/jindolf/util/GUIUtils.java
src/main/java/jp/sfjp/jindolf/view/AccountPanel.java
src/main/java/jp/sfjp/jindolf/view/HelpFrame.java
src/main/java/jp/sfjp/jindolf/view/TopFrame.java [new file with mode: 0644]
src/main/java/jp/sfjp/jindolf/view/TopView.java
src/main/java/jp/sfjp/jindolf/view/WindowManager.java [new file with mode: 0644]
src/test/java/jp/sfjp/jindolf/JreCheckerTest.java [new file with mode: 0644]
src/test/java/jp/sfjp/jindolf/net/HttpUtilsTest.java

index 0a2d842..76e564a 100644 (file)
@@ -9,8 +9,11 @@ Jindolf 変更履歴
     ・JSON入出力処理を Jovsonz プロジェクトへ分離。
     ・ninjin氏の連絡先を http://ninjinix.com へ変更。
     ・UIの各種文言を修正。
-    ・L&Fを「MacOSX」から変更する際の例外に対処。(バグ報告#26564)
+    ・L&Fを「MacOSX」から変更する際の例外に対処。(バグ報告#26564,#15046)
+    ・テキスト系ポップアップメニューにL&F変更が反映されないバグに対処。
+     (バグ報告#27668)
     ・パッケージ構成の整理。
+    ・スタートアップクラスの変更。
 
 3.206.4 (2011-05-10)
     ・10億をこえる発言番号へのアンカー参照を抑止。(バグ報告#24477,#24946)
diff --git a/pom.xml b/pom.xml
index 69dac4b..0c8b190 100644 (file)
--- a/pom.xml
+++ b/pom.xml
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-surefire-plugin</artifactId>
-                <version>2.10</version>
+                <version>2.12</version>
                 <configuration>
                     <skipTests>false</skipTests>
                     <enableAssertions>true</enableAssertions>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-checkstyle-plugin</artifactId>
-                <version>2.8</version>
+                <version>2.9.1</version>
                 <!-- config from property value -->
             </plugin>
 
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-pmd-plugin</artifactId>
-                <version>2.6</version>
+                <version>2.7.1</version>
                 <configuration>
                     <targetJdk>${maven.compiler.target}</targetJdk>
                     <rulesets>
             <plugin>
                 <groupId>org.codehaus.mojo</groupId>
                 <artifactId>findbugs-maven-plugin</artifactId>
-                <version>2.3.2</version>
+                <version>2.3.3</version> <!-- 2.4.0 has BUG-->
                 <configuration>
                     <effort>Max</effort>
                     <threshold>Low</threshold>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-checkstyle-plugin</artifactId>
-                <version>2.8</version>
+                <version>2.9.1</version>
                 <configuration>
                     <skip>false</skip>
                     <!-- config from property value -->
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-pmd-plugin</artifactId>
-                <version>2.6</version>
+                <version>2.7.1</version>
                 <configuration>
                     <skip>false</skip>
                     <targetJdk>${maven.compiler.target}</targetJdk>
             <plugin>
                 <groupId>org.codehaus.mojo</groupId>
                 <artifactId>findbugs-maven-plugin</artifactId>
-                <version>2.4.0</version>
+                <version>2.3.3</version> <!-- 2.4.0 has BUG-->
                 <configuration>
                     <skip>true</skip>
                     <effort>Max</effort>
index b089feb..2d1fc52 100644 (file)
@@ -7,18 +7,13 @@
 
 package jp.sfjp.jindolf;
 
-import java.awt.BorderLayout;
 import java.awt.Component;
-import java.awt.Container;
 import java.awt.Cursor;
 import java.awt.EventQueue;
 import java.awt.Frame;
-import java.awt.LayoutManager;
 import java.awt.Window;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
-import java.awt.event.KeyAdapter;
-import java.awt.event.MouseAdapter;
 import java.awt.event.WindowAdapter;
 import java.awt.event.WindowEvent;
 import java.io.File;
@@ -27,19 +22,17 @@ import java.io.UnsupportedEncodingException;
 import java.lang.reflect.InvocationTargetException;
 import java.net.URL;
 import java.net.URLEncoder;
-import java.util.HashMap;
+import java.text.MessageFormat;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.SortedSet;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.logging.Handler;
+import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.regex.Pattern;
 import javax.swing.JButton;
-import javax.swing.JComponent;
 import javax.swing.JDialog;
-import javax.swing.JFrame;
 import javax.swing.JOptionPane;
 import javax.swing.JToolBar;
 import javax.swing.JTree;
@@ -72,11 +65,11 @@ 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.FontChooser;
 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;
@@ -92,7 +85,9 @@ 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.TopFrame;
 import jp.sfjp.jindolf.view.TopView;
+import jp.sfjp.jindolf.view.WindowManager;
 import jp.sourceforge.jindolf.corelib.LandDef;
 import jp.sourceforge.jindolf.corelib.VillageState;
 import jp.sourceforge.jovsonz.JsObject;
@@ -106,69 +101,43 @@ public class Controller
                    TreeSelectionListener,
                    ChangeListener,
                    AnchorHitListener {
+    private static final Logger LOGGER = Logger.getAnonymousLogger();
 
-    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 static final String ERRTITLE_LAF = "Look&Feel";
+    private static final String ERRFORM_LAF =
+            "このLook&Feel[{0}]を生成する事ができません。";
 
-
-    private final AppSetting appSetting;
-    private final ActionManager actionManager;
-    private final TopView topView;
     private final LandsModel model;
+    private final WindowManager windowManager;
+    private final ActionManager actionManager;
+    private final AppSetting appSetting;
 
-    private final FilterPanel filterFrame;
-    private final LogFrame showlogFrame;
-    private final OptionPanel optionPanel;
-    private final FindPanel findPanel;
-    private final TalkPreview talkPreview;
-    private JFrame helpFrame;
-    private AccountPanel accountFrame;
-    private DaySummary daySummaryPanel;
-    private VillageDigest digestPanel;
-    private final Map<Window, Boolean> windowMap =
-            new HashMap<Window, Boolean>();
+    private final TopView topView;
 
     private volatile boolean isBusyNow;
 
-    private JFrame topFrame = null;
 
     /**
      * コントローラの生成。
-     * @param setting アプリ設定
-     * @param actionManager アクション管理
-     * @param topView 最上位ビュー
      * @param model 最上位データモデル
+     * @param windowManager ウィンドウ管理
+     * @param actionManager アクション管理
+     * @param setting アプリ設定
      */
     @SuppressWarnings("LeakingThisInConstructor")
-    public Controller(AppSetting setting,
-                       ActionManager actionManager,
-                       TopView topView,
-                       LandsModel model){
+    public Controller(LandsModel model,
+                      WindowManager windowManager,
+                      ActionManager actionManager,
+                      AppSetting setting){
         super();
 
         this.appSetting = setting;
         this.actionManager = actionManager;
-        this.topView = topView;
+        this.windowManager = windowManager;
         this.model = model;
 
+        this.topView = this.windowManager.getTopFrame().getTopView();
+
         JToolBar toolbar = this.actionManager.getBrowseToolBar();
         this.topView.setBrowseToolBar(toolbar);
 
@@ -189,101 +158,186 @@ public class Controller
         reloadVillageListButton.addActionListener(this);
         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);
-        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.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.windowMap.put(this.filterFrame,  true);
-        this.windowMap.put(this.showlogFrame, false);
-        this.windowMap.put(this.talkPreview,  false);
-        this.windowMap.put(this.optionPanel,  false);
-        this.windowMap.put(this.findPanel,    true);
+        TopFrame topFrame         = this.windowManager.getTopFrame();
+        TalkPreview talkPreview   = this.windowManager.getTalkPreview();
+        OptionPanel optionPanel   = this.windowManager.getOptionPanel();
+        FindPanel findPanel       = this.windowManager.getFindPanel();
+        FilterPanel filterPanel   = this.windowManager.getFilterPanel();
+        LogFrame logFrame         = this.windowManager.getLogFrame();
+        AccountPanel accountPanel = this.windowManager.getAccountPanel();
+        HelpFrame helpFrame       = this.windowManager.getHelpFrame();
+
+        topFrame.setJMenuBar(this.actionManager.getMenuBar());
+        setFrameTitle(null);
+        topFrame.setDefaultCloseOperation(
+                WindowConstants.DISPOSE_ON_CLOSE);
+        topFrame.addWindowListener(new WindowAdapter(){
+            @Override
+            public void windowClosed(WindowEvent event){
+                shutdown();
+            }
+        });
+
+        filterPanel.addChangeListener(this);
+
+        Handler newHandler = logFrame.getHandler();
+        LogUtils.switchHandler(newHandler);
 
         ConfigStore config = this.appSetting.getConfigStore();
 
         JsObject draft = config.loadDraftConfig();
-        this.talkPreview.putJson(draft);
+        talkPreview.putJson(draft);
 
         JsObject history = config.loadHistoryConfig();
-        this.findPanel.putJson(history);
+        findPanel.putJson(history);
 
         FontInfo fontInfo = this.appSetting.getFontInfo();
         this.topView.getTabBrowser().setFontInfo(fontInfo);
-        this.talkPreview.setFontInfo(fontInfo);
-        this.optionPanel.getFontChooser().setFontInfo(fontInfo);
+        talkPreview.setFontInfo(fontInfo);
+        optionPanel.getFontChooser().setFontInfo(fontInfo);
 
         ProxyInfo proxyInfo = this.appSetting.getProxyInfo();
-        this.optionPanel.getProxyChooser().setProxyInfo(proxyInfo);
+        optionPanel.getProxyChooser().setProxyInfo(proxyInfo);
 
         DialogPref pref = this.appSetting.getDialogPref();
         this.topView.getTabBrowser().setDialogPref(pref);
-        this.optionPanel.getDialogPrefPanel().setDialogPref(pref);
+        optionPanel.getDialogPrefPanel().setDialogPref(pref);
+
+        accountPanel.setModel(this.model);
+
+        OptionInfo optInfo = this.appSetting.getOptionInfo();
+        ConfigStore configStore = this.appSetting.getConfigStore();
+        helpFrame.updateVmInfo(optInfo, configStore);
 
         return;
     }
 
     /**
-     * ã\83\88ã\83\83ã\83\97ã\83\95ã\83¬ã\83¼ã\83 ã\82\92ç\94\9fæ\88\90ã\81\99ã\82\8b
-     * @return ã\83\88ã\83\83ã\83\97ã\83\95ã\83¬ã\83¼ã\83 
+     * ã\82¦ã\82£ã\83³ã\83\89ã\82¦ã\83\9eã\83\8dã\82¸ã\83£ã\82\92è¿\94ã\81\99
+     * @return ã\82¦ã\82£ã\83³ã\83\89ã\82¦ã\83\9eã\83\8dã\82¸ã\83£
      */
-    @SuppressWarnings("serial")
-    public JFrame createTopFrame(){
-        this.topFrame = new JFrame();
+    public WindowManager getWindowManager(){
+        return this.windowManager;
+    }
 
-        Container content = this.topFrame.getContentPane();
-        LayoutManager layout = new BorderLayout();
-        content.setLayout(layout);
-        content.add(this.topView, BorderLayout.CENTER);
+    /**
+     * アプリ最上位フレームを返す。
+     * @return アプリ最上位フレーム
+     */
+    public TopFrame getTopFrame(){
+        TopFrame result = this.windowManager.getTopFrame();
+        return result;
+    }
 
-        Component glassPane = new JComponent() {};
-        glassPane.addMouseListener(new MouseAdapter() {});
-        glassPane.addKeyListener(new KeyAdapter() {});
-        this.topFrame.setGlassPane(glassPane);
+    /**
+     * ビジー状態を設定する。
+     * <p>EDT以外から呼ばれると実際の処理が次回のEDT移行に遅延される。
+     * @param isBusy ビジーならtrue
+     * @param message ステータスバー表示。nullなら変更なし
+     */
+    public void submitBusyStatus(final boolean isBusy, final String message){
+        Runnable task = new Runnable(){
+            @Override
+            public void run(){
+                if(isBusy) setBusy(true);
+                if(message != null) updateStatusBar(message);
+                if( ! isBusy ) setBusy(false);
+                return;
+            }
+        };
 
-        this.topFrame.setJMenuBar(this.actionManager.getMenuBar());
-        setFrameTitle(null);
+        EventQueue.invokeLater(task);
 
-        this.windowMap.put(this.topFrame, false);
+        return;
+    }
 
-        this.topFrame.setDefaultCloseOperation(
-                WindowConstants.DISPOSE_ON_CLOSE);
-        this.topFrame.addWindowListener(new WindowAdapter(){
+    /**
+     * 軽量タスクをEDTで実行する。
+     * <p>タスク実行中はビジー状態となる。
+     * <p>軽量タスク実行中はイベントループが停止するので、
+     * 入出力待ちを伴わなずに早急に終わるタスクでなければならない。
+     * @param task 軽量タスク
+     * @param beforeMsg ビジー中ステータス文字列
+     * @param afterMsg ビジー復帰時のステータス文字列
+     */
+    public void submitLightBusyTask(Runnable task,
+                                    String beforeMsg,
+                                    String afterMsg ){
+        submitBusyStatus(true, beforeMsg);
+        EventQueue.invokeLater(task);
+        submitBusyStatus(false, afterMsg);
+
+        return;
+    }
+
+    /**
+     * 軽量タスクをEDTで実行する。
+     * <p>タスク実行中はビジー状態となる。
+     * <p>軽量タスク実行中はイベントループが停止するので、
+     * 入出力待ちを伴わなずに早急に終わるタスクでなければならない。
+     * <p>タスク終了時、ステータス文字列はタスク実行前の状態に戻る。
+     * @param task 軽量タスク
+     * @param beforeMsg ビジー中ステータス文字列。
+     * ビジー復帰時は元のステータス文字列に戻る。
+     */
+    public void submitLightBusyTask(Runnable task, String beforeMsg){
+        String afterMsg = this.topView.getSysMessage();
+        submitLightBusyTask(task, beforeMsg, afterMsg);
+        return;
+    }
+
+    /**
+     * 重量級タスクをEDTとは別のスレッドで実行する。
+     * <p>タスク実行中はビジー状態となる。
+     * @param heavyTask 重量級タスク
+     * @param beforeMsg ビジー中ステータス文字列
+     * @param afterMsg ビジー復帰時のステータス文字列
+     */
+    public void submitHeavyBusyTask(final Runnable heavyTask,
+                                    final String beforeMsg,
+                                    final String afterMsg ){
+        submitBusyStatus(true, beforeMsg);
+
+        final Runnable busyManager = new Runnable(){
             @Override
-            public void windowClosed(WindowEvent event){
-                shutdown();
+            @SuppressWarnings("CallToThreadYield")
+            public void run(){
+                Thread.yield();
+                try{
+                    heavyTask.run();
+                }finally{
+                    submitBusyStatus(false, afterMsg);
+                }
+                return;
             }
-        });
+        };
+
+        Runnable forkLauncher = new Runnable(){
+            @Override
+            public void run(){
+                Executor executor = Executors.newCachedThreadPool();
+                executor.execute(busyManager);
+                return;
+            }
+        };
+
+        EventQueue.invokeLater(forkLauncher);
 
-        return this.topFrame;
+        return;
+    }
+
+    /**
+     * 重量級タスクをEDTとは別のスレッドで実行する。
+     * <p>タスク実行中はビジー状態となる。
+     * <p>タスク終了時、ステータス文字列はタスク実行前の状態に戻る。
+     * @param task 重量級タスク
+     * @param beforeMsg ビジー中ステータス文字列。
+     * ビジー復帰時は元のステータス文字列に戻る。
+     */
+    public void submitHeavyBusyTask(Runnable task, String beforeMsg){
+        String afterMsg = this.topView.getSysMessage();
+        submitHeavyBusyTask(task, beforeMsg, afterMsg);
+        return;
     }
 
     /**
@@ -296,7 +350,7 @@ public class Controller
                                            JOptionPane.DEFAULT_OPTION,
                                            GUIUtils.getLogoIcon());
 
-        JDialog dialog = pane.createDialog(this.topFrame,
+        JDialog dialog = pane.createDialog(getTopFrame(),
                                            VerInfo.TITLE + "について");
 
         dialog.pack();
@@ -318,23 +372,8 @@ public class Controller
      * Help画面を表示する。
      */
     private void actionHelp(){
-        if(this.helpFrame != null){                 // show Toggle
-            toggleWindow(this.helpFrame);
-            return;
-        }
-
-        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);
-
-        this.windowMap.put(this.helpFrame, false);
-
-        this.helpFrame.setVisible(true);
-
+        HelpFrame helpFrame = this.windowManager.getHelpFrame();
+        toggleWindow(helpFrame);
         return;
     }
 
@@ -356,7 +395,7 @@ public class Controller
             urlText += "#bottom";
         }
 
-        WebIPCDialog.showDialog(this.topFrame, urlText);
+        WebIPCDialog.showDialog(getTopFrame(), urlText);
 
         return;
     }
@@ -385,7 +424,7 @@ public class Controller
                 .append(villageName)
                 .append("%C2%BC.html");
 
-        WebIPCDialog.showDialog(this.topFrame, url.toString());
+        WebIPCDialog.showDialog(getTopFrame(), url.toString());
 
         return;
     }
@@ -414,7 +453,7 @@ public class Controller
 
         url.append("&s=1");
 
-        WebIPCDialog.showDialog(this.topFrame, url.toString());
+        WebIPCDialog.showDialog(getTopFrame(), url.toString());
 
         return;
     }
@@ -441,7 +480,7 @@ public class Controller
         String urlText = url.toString();
         if(period.isHot()) urlText += "#bottom";
 
-        WebIPCDialog.showDialog(this.topFrame, urlText);
+        WebIPCDialog.showDialog(getTopFrame(), urlText);
 
         return;
     }
@@ -471,7 +510,7 @@ public class Controller
 
         String urlText = url.toString();
         urlText += "#" + talk.getMessageID();
-        WebIPCDialog.showDialog(this.topFrame, urlText);
+        WebIPCDialog.showDialog(getTopFrame(), urlText);
 
         return;
     }
@@ -480,7 +519,7 @@ public class Controller
      * ポータルサイトをWebブラウザで表示する。
      */
     private void actionShowPortal(){
-        WebIPCDialog.showDialog(this.topFrame, VerInfo.CONTACT);
+        WebIPCDialog.showDialog(getTopFrame(), VerInfo.CONTACT);
         return;
     }
 
@@ -491,9 +530,9 @@ public class Controller
      * @param e 例外
      */
     private void warnDialog(String title, String message, Throwable e){
-        LOGGER.warn(message, e);
+        LOGGER.log(Level.WARNING, message, e);
         JOptionPane.showMessageDialog(
-            this.topFrame,
+            getTopFrame(),
             message,
             VerInfo.getFrameTitle(title),
             JOptionPane.WARNING_MESSAGE );
@@ -506,82 +545,70 @@ public class Controller
     private void actionChangeLaF(){
         String className = this.actionManager.getSelectedLookAndFeel();
 
-        String warnTitle = "Look&Feel";
-        String warnMsg;
-
         Class<?> lnfClass;
-        warnMsg = "このLook&Feel[" + className + "]を読み込む事ができません。";
         try{
             lnfClass = Class.forName(className);
         }catch(ClassNotFoundException e){
-            warnDialog(warnTitle, warnMsg, e);
+            String warnMsg = MessageFormat.format(
+                    "このLook&Feel[{0}]を読み込む事ができません。",
+                    className );
+            warnDialog(ERRTITLE_LAF, warnMsg, e);
             return;
         }
 
-        LookAndFeel lnf;
-        warnMsg = "このLook&Feel[" + className + "]を生成する事ができません。";
+        final LookAndFeel lnf;
         try{
             lnf = (LookAndFeel)( lnfClass.newInstance() );
         }catch(InstantiationException e){
-            warnDialog(warnTitle, warnMsg, e);
+            String warnMsg = MessageFormat.format(ERRFORM_LAF, className);
+            warnDialog(ERRTITLE_LAF, warnMsg, e);
             return;
         }catch(IllegalAccessException e){
-            warnDialog(warnTitle, warnMsg, e);
+            String warnMsg = MessageFormat.format(ERRFORM_LAF, className);
+            warnDialog(ERRTITLE_LAF, warnMsg, e);
             return;
         }catch(ClassCastException e){
-            warnDialog(warnTitle, warnMsg, e);
+            String warnMsg = MessageFormat.format(ERRFORM_LAF, className);
+            warnDialog(ERRTITLE_LAF, warnMsg, e);
             return;
         }
 
-        warnMsg = "このLook&Feel[" + lnf.getName() + "]はサポートされていません。";
+        Runnable lafTask = new Runnable(){
+            @Override
+            public void run(){
+                changeLaF(lnf);
+                return;
+            }
+        };
+
+        submitLightBusyTask(lafTask,
+                            "Look&Feelを更新中…",
+                            "Look&Feelが更新されました" );
+
+        return;
+    }
+
+    /**
+     * LookAndFeelの実際の更新を行う。
+     * @param lnf LookAndFeel
+     */
+    private void changeLaF(LookAndFeel lnf){
+        assert EventQueue.isDispatchThread();
+
         try{
             UIManager.setLookAndFeel(lnf);
         }catch(UnsupportedLookAndFeelException e){
-            warnDialog(warnTitle, warnMsg, e);
+            String warnMsg = MessageFormat.format(
+                    "このLook&Feel[{0}]はサポートされていません。",
+                    lnf.getName() );
+            warnDialog(ERRTITLE_LAF, warnMsg, e);
             return;
         }
 
-        LOGGER.info(
-                "Look&Feelが["
-                +lnf.getName()
-                +"]に変更されました。");
-
-        final Runnable updateUITask = new Runnable(){
-            public void run(){
-                Set<Window> windows = Controller.this.windowMap.keySet();
-                for(Window window : windows){
-                    SwingUtilities.updateComponentTreeUI(window);
-                    window.validate();
-                    boolean needPack = Controller.this.windowMap.get(window);
-                    if(needPack){
-                        window.pack();
-                    }
-                }
+        this.windowManager.changeAllWindowUI();
 
-                return;
-            }
-        };
-
-        Executor executor = Executors.newCachedThreadPool();
-        executor.execute(new Runnable(){
-            public void run(){
-                setBusy(true);
-                updateStatusBar("Look&Feelを更新中…");
-                try{
-                    SwingUtilities.invokeAndWait(updateUITask);
-                }catch(InvocationTargetException e){
-                    LOGGER.warn(
-                            "Look&Feelの更新に失敗しました。", e);
-                }catch(InterruptedException e){
-                    LOGGER.warn(
-                            "Look&Feelの更新に失敗しました。", e);
-                }finally{
-                    updateStatusBar("Look&Feelが更新されました");
-                    setBusy(false);
-                }
-                return;
-            }
-        });
+        LOGGER.log(Level.INFO,
+                   "Look&Feelが[{0}]に変更されました。", lnf.getName() );
 
         return;
     }
@@ -590,7 +617,8 @@ public class Controller
      * 発言フィルタ画面を表示する。
      */
     private void actionShowFilter(){
-        toggleWindow(this.filterFrame);
+        FilterPanel filterPanel = this.windowManager.getFilterPanel();
+        toggleWindow(filterPanel);
         return;
     }
 
@@ -598,18 +626,8 @@ public class Controller
      * アカウント管理画面を表示する。
      */
     private void actionShowAccount(){
-        if(this.accountFrame != null){                 // show Toggle
-            toggleWindow(this.accountFrame);
-            return;
-        }
-
-        this.accountFrame = new AccountPanel(this.topFrame, this.model);
-        this.accountFrame.setTitle(TITLE_ACCOUNT);
-        this.accountFrame.pack();
-        this.accountFrame.setVisible(true);
-
-        this.windowMap.put(this.accountFrame, true);
-
+        AccountPanel accountPanel = this.windowManager.getAccountPanel();
+        toggleWindow(accountPanel);
         return;
     }
 
@@ -617,7 +635,8 @@ public class Controller
      * ログ表示画面を表示する。
      */
     private void actionShowLog(){
-        toggleWindow(this.showlogFrame);
+        LogFrame logFrame = this.windowManager.getLogFrame();
+        toggleWindow(logFrame);
         return;
     }
 
@@ -625,7 +644,8 @@ public class Controller
      * 発言エディタを表示する。
      */
     private void actionTalkPreview(){
-        toggleWindow(this.talkPreview);
+        TalkPreview talkPreview = this.windowManager.getTalkPreview();
+        toggleWindow(talkPreview);
         return;
     }
 
@@ -633,25 +653,27 @@ public class Controller
      * オプション設定画面を表示する。
      */
     private void actionOption(){
+        OptionPanel optionPanel = this.windowManager.getOptionPanel();
+
         FontInfo fontInfo = this.appSetting.getFontInfo();
-        this.optionPanel.getFontChooser().setFontInfo(fontInfo);
+        optionPanel.getFontChooser().setFontInfo(fontInfo);
 
         ProxyInfo proxyInfo = this.appSetting.getProxyInfo();
-        this.optionPanel.getProxyChooser().setProxyInfo(proxyInfo);
+        optionPanel.getProxyChooser().setProxyInfo(proxyInfo);
 
         DialogPref dialogPref = this.appSetting.getDialogPref();
-        this.optionPanel.getDialogPrefPanel().setDialogPref(dialogPref);
+        optionPanel.getDialogPrefPanel().setDialogPref(dialogPref);
 
-        this.optionPanel.setVisible(true);
-        if(this.optionPanel.isCanceled()) return;
+        optionPanel.setVisible(true);
+        if(optionPanel.isCanceled()) return;
 
-        fontInfo = this.optionPanel.getFontChooser().getFontInfo();
+        fontInfo = optionPanel.getFontChooser().getFontInfo();
         updateFontInfo(fontInfo);
 
-        proxyInfo = this.optionPanel.getProxyChooser().getProxyInfo();
+        proxyInfo = optionPanel.getProxyChooser().getProxyInfo();
         updateProxyInfo(proxyInfo);
 
-        dialogPref = this.optionPanel.getDialogPrefPanel().getDialogPref();
+        dialogPref = optionPanel.getDialogPrefPanel().getDialogPref();
         updateDialogPref(dialogPref);
 
         return;
@@ -668,8 +690,13 @@ public class Controller
         this.appSetting.setFontInfo(newFontInfo);
 
         this.topView.getTabBrowser().setFontInfo(newFontInfo);
-        this.talkPreview.setFontInfo(newFontInfo);
-        this.optionPanel.getFontChooser().setFontInfo(newFontInfo);
+
+        TalkPreview talkPreview = this.windowManager.getTalkPreview();
+        OptionPanel optionPanel = this.windowManager.getOptionPanel();
+        FontChooser fontChooser = optionPanel.getFontChooser();
+
+        talkPreview.setFontInfo(newFontInfo);
+        fontChooser.setFontInfo(newFontInfo);
 
         return;
     }
@@ -725,22 +752,15 @@ public class Controller
             JOptionPane pane = new JOptionPane(message,
                                                JOptionPane.WARNING_MESSAGE,
                                                JOptionPane.DEFAULT_OPTION );
-            JDialog dialog = pane.createDialog(this.topFrame, title);
+            JDialog dialog = pane.createDialog(getTopFrame(), title);
             dialog.pack();
             dialog.setVisible(true);
             dialog.dispose();
             return;
         }
 
-        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);
-        }
-
-        final VillageDigest digest = this.digestPanel;
+        VillageDigest villageDigest = this.windowManager.getVillageDigest();
+        final VillageDigest digest = villageDigest;
         Executor executor = Executors.newCachedThreadPool();
         executor.execute(new Runnable(){
             public void run(){
@@ -797,12 +817,14 @@ public class Controller
      * 検索パネルを表示する。
      */
     private void actionShowFind(){
-        this.findPanel.setVisible(true);
-        if(this.findPanel.isCanceled()){
+        FindPanel findPanel = this.windowManager.getFindPanel();
+
+        findPanel.setVisible(true);
+        if(findPanel.isCanceled()){
             updateFindPanel();
             return;
         }
-        if(this.findPanel.isBulkSearch()){
+        if(findPanel.isBulkSearch()){
             bulkSearch();
         }else{
             regexSearch();
@@ -817,7 +839,8 @@ public class Controller
         Discussion discussion = currentDiscussion();
         if(discussion == null) return;
 
-        RegexPattern regPattern = this.findPanel.getRegexPattern();
+        FindPanel findPanel = this.windowManager.getFindPanel();
+        RegexPattern regPattern = findPanel.getRegexPattern();
         int hits = discussion.setRegexPattern(regPattern);
 
         String hitMessage = "[" + hits + "]件ヒットしました";
@@ -855,7 +878,8 @@ public class Controller
     private void taskBulkSearch(){
         taskLoadAllPeriod();
         int totalhits = 0;
-        RegexPattern regPattern = this.findPanel.getRegexPattern();
+        FindPanel findPanel = this.windowManager.getFindPanel();
+        RegexPattern regPattern = findPanel.getRegexPattern();
         StringBuilder hitDesc = new StringBuilder();
         TabBrowser browser = this.topView.getTabBrowser();
         for(PeriodView periodView : browser.getPeriodViewList()){
@@ -894,7 +918,8 @@ public class Controller
         Discussion discussion = currentDiscussion();
         if(discussion == null) return;
         RegexPattern pattern = discussion.getRegexPattern();
-        this.findPanel.setRegexPattern(pattern);
+        FindPanel findPanel = this.windowManager.getFindPanel();
+        findPanel.setRegexPattern(pattern);
         return;
     }
 
@@ -908,17 +933,9 @@ public class Controller
         Period period = periodView.getPeriod();
         if(period == null) return;
 
-        if(this.daySummaryPanel == null){
-            this.daySummaryPanel = new DaySummary(this.topFrame);
-            this.daySummaryPanel.setTitle(TITLE_DAYSUMMARY);
-            this.daySummaryPanel.pack();
-            this.daySummaryPanel.setSize(400, 500);
-        }
-
-        this.daySummaryPanel.summaryPeriod(period);
-        this.daySummaryPanel.setVisible(true);
-
-        this.windowMap.put(this.daySummaryPanel, false);
+        DaySummary daySummary = this.windowManager.getDaySummary();
+        daySummary.summaryPeriod(period);
+        daySummary.setVisible(true);
 
         return;
     }
@@ -933,7 +950,8 @@ public class Controller
         Period period = periodView.getPeriod();
         if(period == null) return;
 
-        File file = CsvExporter.exportPeriod(period, this.filterFrame);
+        FilterPanel filterPanel = this.windowManager.getFilterPanel();
+        File file = CsvExporter.exportPeriod(period, filterPanel);
         if(file != null){
             String message = "CSVファイル("
                             +file.getName()
@@ -986,7 +1004,7 @@ public class Controller
         Period period = discussion.getPeriod();
         if(period == null) return;
         if(period.getTopics() > 1000){
-            JOptionPane.showMessageDialog(this.topFrame,
+            JOptionPane.showMessageDialog(getTopFrame(),
                     "エピローグが1000発言を超えはじめたら、\n"
                     +"負荷対策のためWebブラウザによるアクセスを"
                     +"心がけましょう",
@@ -1065,7 +1083,7 @@ public class Controller
 
         this.topView.showInitPanel();
 
-        execReloadVillageList(land);
+        submitReloadVillageList(land);
 
         return;
     }
@@ -1169,30 +1187,44 @@ public class Controller
     }
 
     /**
-     * 指定した国の村一覧を読み込む。
+     * æ\8c\87å®\9aã\81\97ã\81\9få\9b½ã\81®æ\9d\91ä¸\80覧ã\82\92読ã\81¿è¾¼ã\82\80ã\82¸ã\83§ã\83\96ã\82\92æ\8a\95ä¸\8bã\80\82
      * @param land 国
      */
-    private void execReloadVillageList(final Land land){
-        final LandsTree treePanel = this.topView.getLandsTree();
-        Executor executor = Executors.newCachedThreadPool();
-        executor.execute(new Runnable(){
+    private void submitReloadVillageList(final Land land){
+        Runnable heavyTask = new Runnable(){
+            @Override
             public void run(){
-                setBusy(true);
-                updateStatusBar("村一覧を読み込み中…");
-                try{
-                    try{
-                        Controller.this.model.loadVillageList(land);
-                    }catch(IOException e){
-                        showNetworkError(land, e);
-                    }
-                    treePanel.expandLand(land);
-                }finally{
-                    updateStatusBar("村一覧の読み込み完了");
-                    setBusy(false);
-                }
+                taskReloadVillageList(land);
                 return;
             }
-        });
+        };
+
+        submitHeavyBusyTask(heavyTask,
+                            "村一覧を読み込み中…",
+                            "村一覧の読み込み完了" );
+
+        return;
+    }
+
+    /**
+     * 指定した国の村一覧を読み込む。(ヘビータスク本体)
+     * @param land 国
+     */
+    private void taskReloadVillageList(Land land){
+        SortedSet<Village> villageList;
+        try{
+            villageList = land.downloadVillageList();
+        }catch(IOException e){
+            showNetworkError(land, e);
+            return;
+        }
+        land.updateVillageList(villageList);
+
+        this.model.updateVillageList(land);
+
+        LandsTree treePanel = this.topView.getLandsTree();
+        treePanel.expandLand(land);
+
         return;
     }
 
@@ -1209,7 +1241,8 @@ public class Controller
         final PeriodView periodView = currentPeriodView();
         Discussion discussion = currentDiscussion();
         if(discussion == null) return;
-        discussion.setTopicFilter(this.filterFrame);
+        FilterPanel filterPanel = this.windowManager.getFilterPanel();
+        discussion.setTopicFilter(filterPanel);
         final Period period = discussion.getPeriod();
         if(period == null) return;
 
@@ -1263,10 +1296,10 @@ public class Controller
                         }
                     });
                 }catch(InvocationTargetException e){
-                    LOGGER.fatal(
+                    LOGGER.log(Level.SEVERE,
                             "タブ操作で致命的な障害が発生しました", e);
                 }catch(InterruptedException e){
-                    LOGGER.fatal(
+                    LOGGER.log(Level.SEVERE,
                             "タブ操作で致命的な障害が発生しました", e);
                 }
                 updateStatusBar("村情報を読み直しました…");
@@ -1285,11 +1318,13 @@ public class Controller
                             }
                         });
                     }catch(InvocationTargetException e){
-                        LOGGER.fatal(
-                                "ブラウザ表示で致命的な障害が発生しました", e);
+                        LOGGER.log(Level.SEVERE,
+                                "ブラウザ表示で致命的な障害が発生しました",
+                                e );
                     }catch(InterruptedException e){
-                        LOGGER.fatal(
-                                "ブラウザ表示で致命的な障害が発生しました", e);
+                        LOGGER.log(Level.SEVERE,
+                                "ブラウザ表示で致命的な障害が発生しました",
+                                e );
                     }
                     EventQueue.invokeLater(new Runnable(){
                         public void run(){
@@ -1312,22 +1347,11 @@ public class Controller
     private void filterChanged(){
         final Discussion discussion = currentDiscussion();
         if(discussion == null) return;
-        discussion.setTopicFilter(this.filterFrame);
 
-        Executor executor = Executors.newCachedThreadPool();
-        executor.execute(new Runnable(){
-            public void run(){
-                setBusy(true);
-                updateStatusBar("フィルタリング中…");
-                try{
-                    discussion.filtering();
-                }finally{
-                    updateStatusBar("フィルタリング完了");
-                    setBusy(false);
-                }
-                return;
-            }
-        });
+        FilterPanel filterPanel = this.windowManager.getFilterPanel();
+
+        discussion.setTopicFilter(filterPanel);
+        discussion.filtering();
 
         return;
     }
@@ -1400,7 +1424,7 @@ public class Controller
      * @param e ネットワークエラー
      */
     public void showNetworkError(Land land, IOException e){
-        LOGGER.warn("ネットワークで障害が発生しました", e);
+        LOGGER.log(Level.WARNING, "ネットワークで障害が発生しました", e);
 
         ServerAccess server = land.getServerAccess();
         String message =
@@ -1416,7 +1440,7 @@ public class Controller
                                            JOptionPane.DEFAULT_OPTION );
 
         String title = VerInfo.getFrameTitle("通信異常発生");
-        JDialog dialog = pane.createDialog(this.topFrame, title);
+        JDialog dialog = pane.createDialog(getTopFrame(), title);
 
         dialog.pack();
         dialog.setVisible(true);
@@ -1489,7 +1513,7 @@ public class Controller
     public void stateChanged(ChangeEvent event){
         Object source = event.getSource();
 
-        if(source == this.filterFrame){
+        if(source == this.windowManager.getFilterPanel()){
             filterChanged();
         }else if(source instanceof TabBrowser){
             updateFindPanel();
@@ -1600,7 +1624,7 @@ public class Controller
             return;
         }
 
-        execReloadVillageList(land);
+        submitReloadVillageList(land);
 
         return;
     }
@@ -1685,7 +1709,7 @@ public class Controller
                     cursor = Cursor.getDefaultCursor();
                 }
 
-                Component glass = Controller.this.topFrame.getGlassPane();
+                Component glass = getTopFrame().getGlassPane();
                 glass.setCursor(cursor);
                 glass.setVisible(isBusy);
                 Controller.this.topView.setBusy(isBusy);
@@ -1700,9 +1724,9 @@ public class Controller
             try{
                 SwingUtilities.invokeAndWait(microJob);
             }catch(InvocationTargetException e){
-                LOGGER.fatal("ビジー処理で失敗", e);
+                LOGGER.log(Level.SEVERE, "ビジー処理で失敗", e);
             }catch(InterruptedException e){
-                LOGGER.fatal("ビジー処理で失敗", e);
+                LOGGER.log(Level.SEVERE, "ビジー処理で失敗", e);
             }
         }
 
@@ -1724,7 +1748,8 @@ public class Controller
      */
     private void setFrameTitle(String name){
         String title = VerInfo.getFrameTitle(name);
-        this.topFrame.setTitle(title);
+        TopFrame topFrame = this.windowManager.getTopFrame();
+        topFrame.setTitle(title);
         return;
     }
 
@@ -1734,13 +1759,15 @@ public class Controller
     private void shutdown(){
         ConfigStore configStore = this.appSetting.getConfigStore();
 
-        JsObject findConf = this.findPanel.getJson();
-        if( ! this.findPanel.hasConfChanged(findConf) ){
+        FindPanel findPanel = this.windowManager.getFindPanel();
+        JsObject findConf = findPanel.getJson();
+        if( ! findPanel.hasConfChanged(findConf) ){
             configStore.saveHistoryConfig(findConf);
         }
 
-        JsObject draftConf = this.talkPreview.getJson();
-        if( ! this.talkPreview.hasConfChanged(draftConf) ){
+        TalkPreview talkPreview = this.windowManager.getTalkPreview();
+        JsObject draftConf = talkPreview.getJson();
+        if( ! talkPreview.hasConfChanged(draftConf) ){
             configStore.saveDraftConfig(draftConf);
         }
 
index ecfa30f..40cdead 100644 (file)
@@ -9,29 +9,41 @@ package jp.sfjp.jindolf;
 
 /**
  * Jindolf スタートアップクラス。
+ * <p>起動用のmainエントリを提供する。
  * <p>JRE実行系の互換性検査を主目的とする。
  * <p>このクラスではJRE1.0互換なランタイムライブラリのみが利用できる。
- * <p>このクラスはJRE1.0でもコンパイルできなければならない。
- * アプリ開始はstaticメソッド{@link #main(String[])}呼び出しから。
+ * このクラスはJDK1.0相当のコンパイラでもコンパイルできなければならない。
+ * <p>アプリ開始はstaticメソッド{@link #main(String[])}呼び出しから。
  */
 public final class Jindolf {
 
     /**
      * 隠しコンストラクタ。
+     * <p><code>assert false;</code> 書きたいけど書いちゃだめ。
      */
     private Jindolf(){
-        super();
         return;
     }
 
+
     /**
      * Jindolf のスタートアップエントリ。
+     * <p>互換性検査が行われた後、
+     * JRE1.5解除版エントリ{@link JindolfJre15}
+     * に制御を渡す。
      * @param args コマンドライン引数
      */
     public static void main(String[] args){
-        JreChecker.checkJre();
+        int exitCode;
+
+        exitCode = JreChecker.checkJre();
+        if(exitCode != 0) System.exit(exitCode);
+
+        exitCode = JindolfJre15.main(args);
+        if(exitCode != 0) System.exit(exitCode);
 
-        JindolfJre15.main(args);
+        // デーモンスレッドがいなければ、(アプリ画面が出ていなければ)
+        // この後暗黙にSystem.exit(0)が行われる。はず。
 
         return;
     }
diff --git a/src/main/java/jp/sfjp/jindolf/JindolfGuiJp.java b/src/main/java/jp/sfjp/jindolf/JindolfGuiJp.java
deleted file mode 100644 (file)
index e4c1f57..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * 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;
-    }
-
-}
index 7e58b81..131ad45 100644 (file)
@@ -7,7 +7,11 @@
 
 package jp.sfjp.jindolf;
 
+import java.awt.Frame;
 import java.awt.GraphicsEnvironment;
+import java.io.PrintStream;
+import java.text.MessageFormat;
+import java.util.concurrent.atomic.AtomicBoolean;
 import javax.swing.JOptionPane;
 
 /**
@@ -15,59 +19,86 @@ import javax.swing.JOptionPane;
  * <p>起動クラスJindolfの下請けとしての機能が想定される。
  * <p>必ずしも必要ないが、異常系切り分けに有用な、
  * 実行環境やビルドの成否に関する各種診断を行う。
+ * <p>各種診断を通過した後、JindolfMainに制御を渡す。
  */
 public final class JindolfJre15 {
 
-    /** exit code. */
+    /** GUI環境に接続できないときの終了コード。 */
     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;
+    /** アプリのビルド工程の不備を自動検出した時の終了コード。 */
+    public static final int EXIT_CODE_BUILDMISS = 1;
+    /** VM内多重起動を自動検出した時の終了コード。 */
+    public static final int EXIT_CODE_INVOKEDBL = 1;
+
+    private static final PrintStream STDERR = System.err;
+
+    private static final String ERRMSG_GUISESS =
+            "ERROR : GUI session failed.";
+    private static final String ERRMSG_DISPLAY =
+            "{0}\nEnvironment: DISPLAY [{1}]";
+
+    private static final String TITLE_SRCENC = "Build failed";
+    private static final String ERRMSG_SRCENC =
+              "ERROR : Invalid source-code encoding detected.\n"
+            + "Let's check encoding with compiler & source-file.";
+
+    private static final String TITLE_PKGERR = "ビルドエラー";
+    private static final String ERRMSG_PKG =
+            "パッケージ定義と{0}が一致しません。[{1}]≠[{2}]";
+
+    private static final String TITLE_INVOKEDBL = "多重起動";
+    private static final String ERRMSG_INVOKEDBL =
+            "ERROR : 二度目以降の起動がキャンセルされました。";
+
+    private static final char[][] ENCODING_TEST = {
+        {'狼', 0x72fc},
+        {' ', 0x3000},  // 全角スペース
+        {'\\', 0x005c},  // バックスラッシュ
+        {'¥',  0x00a5},  // 半角円通貨
+        {'~',  0x007e},  // チルダ
+        {'~', 0xff5e},  // 全角チルダ
+        {'〜', 0x301c},  // 波ダッシュ
+        {'�', 0xfffd},  // Unicode専用特殊文字
+    };
+
+    /** 多重起動防止用セマフォ。 */
+    private static final AtomicBoolean INVOKE_FLAG =
+            new AtomicBoolean(false);
 
 
     /**
      * 隠しコンストラクタ。
      */
     private JindolfJre15(){
-        super();
         assert false;
-        return;
     }
 
 
     /**
      * GUI環境の有無をチェックする。
      * GUI環境に接続できなければJVMを終了させる。
+     * @return 成功すれば0。失敗したら0以外。
      */
-    private static void checkGuiEnv(){
-        if( ! GraphicsEnvironment.isHeadless() ) return;
+    private static int proveGuiEnv(){
+        if( ! GraphicsEnvironment.isHeadless() ) return 0;
+
+        String errMsg = ERRMSG_GUISESS;
 
         String dispEnv;
         try{
-            dispEnv = System.getenv("DISPLAY");
+            dispEnv = System.getenv("DISPLAY"); // for X11
         }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("]");
+            errMsg = MessageFormat.format(ERRMSG_DISPLAY, errMsg, dispEnv);
         }
 
-        System.err.println(message);
-        System.err.flush();
-
-        System.exit(EXIT_CODE_HEADLESS);
+        STDERR.println(errMsg);
+        STDERR.flush();
 
-        assert false;
-        return;
+        return EXIT_CODE_HEADLESS;
     }
 
     /**
@@ -76,7 +107,8 @@ public final class JindolfJre15 {
      * @param title タイトル
      */
     private static void showErrorDialog(String errmsg, String title){
-        JOptionPane.showMessageDialog(null,
+        Frame parent = null;
+        JOptionPane.showMessageDialog(parent,
                                       errmsg,
                                       title,
                                       JOptionPane.ERROR_MESSAGE);
@@ -85,87 +117,106 @@ public final class JindolfJre15 {
 
     /**
      * ビルド時のエンコーディングミスを判定する。
-     * ※ 非Unicode系の開発環境を使いたい人は適当に無視してね。
+     * <p>※ 非Unicode系の開発環境を使いたい人は適当に無視してね。
+     * @return 成功すれば0。失敗したら0以外。
      */
-    private static void checkBuildError(){
-        if(   '狼' == 0x72fc
-           && ' ' == 0x3000
-           && '~'  == 0x007e
-           && '\\' == 0x005c  // バックスラッシュ
-           && '¥'  == 0x00a5  // 半角円通貨
-           && '~' == 0xff5e
-           && '�' == 0xfffd  // Unicode専用特殊文字
-        ){
-            return;
+    private static int checkSourceEncoding(){
+        boolean pass = true;
+        for(char[] pair : ENCODING_TEST){
+            char compiled = pair[0];
+            char expected = pair[1];
+            if(compiled != expected){
+                pass = false;
+                break;
+            }
         }
+        if(pass) return 0;
 
-        String errmsg =
-                "ERROR : Invalid source-code encoding detected.\n"
-                +"Let's check encoding with compiler & source-file.";
-
-        showErrorDialog(errmsg, "Build failed");
+        String errmsg = ERRMSG_SRCENC;
 
-        System.exit(EXIT_CODE_BUILDENCO);
+        showErrorDialog(errmsg, TITLE_SRCENC);
 
-        assert false;
-        return;
+        return EXIT_CODE_BUILDMISS;
     }
 
     /**
      * MANIFEST.MFパッケージ定義エラーの検出。
+     * @return 成功すれば0。失敗したら0以外。
      */
-    private static void checkPackageDefinition(){
+    private static int checkPackageDefinition(){
         Package rootPkg = ResourceManager.DEF_ROOT_PACKAGE;
         String implTitle   = rootPkg.getImplementationTitle();
         String implVersion = rootPkg.getImplementationVersion();
         String implVendor  = rootPkg.getImplementationVendor();
 
-        String title = "ビルドエラー";
+        String title = TITLE_PKGERR;
 
         if(implTitle != null && ! VerInfo.TITLE.equals(implTitle) ){
-            String errmsg = "パッケージ定義とタイトルが一致しません。"
-                    +"["+ implTitle +"]≠["+ VerInfo.TITLE +"]";
+            String errmsg = MessageFormat.format(
+                    ERRMSG_PKG, "タイトル", implTitle, VerInfo.TITLE );
             showErrorDialog(errmsg, title);
-            System.exit(EXIT_CODE_INVMANIFEST);
-            assert false;
+            return EXIT_CODE_BUILDMISS;
         }
 
         if(implVersion != null && ! VerInfo.VERSION.equals(implVersion)  ){
-            String errmsg = "パッケージ定義とバージョン番号が一致しません。"
-                    +"["+ implVersion +"]≠["+ VerInfo.VERSION +"]";
+            String errmsg = MessageFormat.format(
+                    ERRMSG_PKG,
+                    "バージョン番号", implVersion, VerInfo.VERSION );
             showErrorDialog(errmsg, title);
-            System.exit(EXIT_CODE_INVMANIFEST);
-            assert false;
+            return EXIT_CODE_BUILDMISS;
         }
 
         if(implVendor != null && ! VerInfo.AUTHOR.equals(implVendor) ){
-            String errmsg = "パッケージ定義とベンダが一致しません。"
-                    +"["+ implVendor +"]≠["+ VerInfo.AUTHOR +"]";
+            String errmsg = MessageFormat.format(
+                    ERRMSG_PKG, "ベンダ", implVendor, VerInfo.AUTHOR );
             showErrorDialog(errmsg, title);
-            System.exit(EXIT_CODE_INVMANIFEST);
-            assert false;
+            return EXIT_CODE_BUILDMISS;
         }
 
-        return;
+        return 0;
+    }
+
+    /**
+     * JVM内で多重起動していないかチェックする。
+     * <p>多重起動を確認した場合は、GUIにダイアログを出力する。
+     * @return 成功すれば0。失敗したら0以外。
+     */
+    private static int checkHasInvoked(){
+        boolean successed = INVOKE_FLAG.compareAndSet(false, true);
+        if(successed) return 0;
+
+        showErrorDialog(ERRMSG_INVOKEDBL, TITLE_INVOKEDBL);
+
+        return EXIT_CODE_INVOKEDBL;
     }
 
     /**
      * Jindolf のスタートアップエントリ。
      * <p>ここからJRE1.5の利用が解禁される。
+     * <p>最終的に{@link JindolfMain}へ制御が渡される。
      * @param args コマンドライン引数
+     * @return 起動に成功すれば0。失敗したら0以外。
      */
-    static void main(String... args){
-        checkGuiEnv();
-        // ここから異常系でのSwingGUI解禁
+    public static int main(String... args){
+        int exitCode;
 
-        checkBuildError();
-        // ここからUnicode出力解禁。
+        exitCode = proveGuiEnv();
+        if(exitCode != 0) return exitCode;
+        // ここから異常系でのみSwingGUI解禁
 
-        checkPackageDefinition();
+        exitCode = checkSourceEncoding();
+        if(exitCode != 0) return exitCode;
+        // ここから日本語を含むUnicode出力解禁。
 
-        JindolfGuiJp.main(args);
+        exitCode = checkPackageDefinition();
+        if(exitCode != 0) return exitCode;
 
-        return;
+        exitCode = checkHasInvoked();
+        if(exitCode != 0) return exitCode;
+
+        exitCode = JindolfMain.main(args);
+
+        return exitCode;
     }
 
 }
@@ -10,10 +10,11 @@ package jp.sfjp.jindolf;
 import java.awt.Dimension;
 import java.awt.EventQueue;
 import java.awt.Window;
+import java.io.PrintStream;
 import java.lang.reflect.InvocationTargetException;
-import java.text.DateFormat;
-import java.text.NumberFormat;
-import java.util.Date;
+import java.text.MessageFormat;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import javax.swing.ImageIcon;
 import javax.swing.JFrame;
 import javax.swing.JLabel;
@@ -21,34 +22,21 @@ 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;
+import jp.sfjp.jindolf.view.WindowManager;
 
 /**
  * Jindolf スタートアップクラス。
- *
- * コンストラクタは無いよ。
- * アプリ開始はstaticメソッド{@link #main(String[])}呼び出しから。
+ * <p>{@link JindolfJre15}の下請けとして本格的なJindolf起動処理に入る。
  */
-public final class JindolfOld {
-
-    /** このClass。 */
-    public static final Class<?> SELF_KLASS;
-    /** クラスローダ。 */
-    public static final ClassLoader LOADER;
-
+public final class JindolfMain {
 
     /** クラスロード時のナノカウント。 */
     public static final long NANOCT_LOADED;
@@ -57,52 +45,69 @@ public final class JindolfOld {
 
 
     /** ロガー。 */
-    private static final LogWrapper LOGGER = new LogWrapper();
+    private static final Logger LOGGER = Logger.getAnonymousLogger();
 
     /** スプラッシュロゴ。 */
     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;
+    private static final String LOG_LOADED =
+              "{0} は {1,date} {2,time} に"
+            + "VM上のクラス {3} としてロードされました。 ";
+    private static final String LOG_NANOCT =
+            "Initial Nano-Count : {0}";
+    private static final String LOG_HEAP =
+            "Max-heap : {0} Bytes   Total-heap : {1} Bytes";
+    private static final String LOG_CONF =
+            "設定格納ディレクトリに[ {0} ]が指定されました。";
+    private static final String LOG_NOCONF =
+            "設定格納ディレクトリは使いません。";
+    private static final String WARNMSG_SPLASH =
+              "JRE1.6以降では、Jindolfの-nosplashオプションは無効です。"
+            + "Java実行系の方でスプラッシュ画面の非表示を"
+            + "指示してください(おそらく空の-splash:オプション)";
+    private static final String FATALMSG_INITFAIL =
+            "アプリケーション初期化に失敗しました";
+    private static final String ERRMSG_HELP =
+              "起動オプション一覧は、"
+            + "起動オプションに「{0}」を指定すると確認できます。";
+
+    private static final PrintStream STDOUT = System.out;
+    private static final PrintStream STDERR = System.err;
 
+    static{
         NANOCT_LOADED  = System.nanoTime();
         EPOCHMS_LOADED = System.currentTimeMillis();
-
-        new JindolfOld().hashCode();
     }
 
 
     /**
      * 隠れコンストラクタ。
      */
-    private JindolfOld(){
-        super();
-        assert this.getClass() == SELF_KLASS;
-        return;
+    private JindolfMain(){
+        assert false;
     }
 
 
     /**
+     * 標準出力および標準エラー出力をフラッシュする。
+     */
+    private static void flush(){
+        STDOUT.flush();
+        STDERR.flush();
+        return;
+    }
+
+    /**
      * 標準出力端末にヘルプメッセージ(オプションの説明)を表示する。
      */
     private static void showHelpMessage(){
-        System.out.flush();
-        System.err.flush();
+        flush();
 
-        CharSequence helpText = CmdOption.getHelpText();
-        System.out.print(helpText);
+        String helpText = CmdOption.getHelpText();
+        STDOUT.print(helpText);
 
-        System.out.flush();
-        System.err.flush();
+        flush();
 
         return;
     }
@@ -135,43 +140,45 @@ public final class JindolfOld {
      * スプラッシュウィンドウを隠す。
      * @param splashWindow スプラッシュウィンドウ。nullならなにもしない。
      */
-    @SuppressWarnings("CallToThreadYield")
-    private static void hideSplash(Window splashWindow){
+    private static void hideSplash(final Window splashWindow){
         if(splashWindow == null) return;
 
-        splashWindow.setVisible(false);
-        splashWindow.dispose();
-
-        Thread.yield();
+        EventQueue.invokeLater(new Runnable(){
+            public void run(){
+                splashWindow.setVisible(false);
+                splashWindow.dispose();
+                return;
+            }
+        });
 
         return;
     }
 
     /**
      * 起動時の諸々の情報をログ出力する。
-     * @param optinfo コマンドライン情報
-     * @param configStore 設定ディレクトリ情報
+     * @param appSetting  アプリ設定
      */
-    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() + " としてロードされました。 " );
+    private static void dumpBootInfo(AppSetting appSetting){
+        Object[] logArgs;
+
+        logArgs = new Object[]{
+            VerInfo.ID,
+            EPOCHMS_LOADED,
+            EPOCHMS_LOADED,
+            Jindolf.class.getName(),
+        };
+        LOGGER.log(Level.INFO, LOG_LOADED, logArgs);
 
-        LOGGER.info("Initial Nano-Count : " + nform.format(NANOCT_LOADED));
+        LOGGER.log(Level.INFO, LOG_NANOCT, NANOCT_LOADED);
 
         Runtime runtime = Runtime.getRuntime();
-        LOGGER.info(
-                "Max-heap : "
-                + nform.format(runtime.maxMemory()) + " Byte"
-                + "   Total-heap : "
-                + nform.format(runtime.totalMemory()) + " Byte");
+        logArgs = new Object[]{
+            runtime.maxMemory(),
+            runtime.totalMemory(),
+        };
+        LOGGER.log(Level.INFO, LOG_HEAP, logArgs);
 
+        OptionInfo optinfo = appSetting.getOptionInfo();
         StringBuilder bootArgs = new StringBuilder();
         bootArgs.append("\n\n").append("起動時引数:\n");
         for(String arg : optinfo.getInvokeArgList()){
@@ -179,126 +186,69 @@ public final class JindolfOld {
         }
         bootArgs.append('\n');
         bootArgs.append(EnvInfo.getVMInfo());
-        LOGGER.info(bootArgs);
+        LOGGER.info(bootArgs.toString());
 
+        ConfigStore configStore = appSetting.getConfigStore();
         if(configStore.useStoreFile()){
-            LOGGER.info("設定格納ディレクトリに[ "
-                    + configStore.getConfigPath().getPath()
-                    + " ]が指定されました。");
+            LOGGER.log(Level.INFO, LOG_CONF, configStore.getConfigPath());
         }else{
-            LOGGER.info("設定格納ディレクトリは使いません。");
+            LOGGER.info(LOG_NOCONF);
         }
 
         if(   JreChecker.has16Runtime()
            && optinfo.hasOption(CmdOption.OPT_NOSPLASH) ){
-            LOGGER.warn(
-                      "JRE1.6以降では、"
-                    +"Jindolfの-nosplashオプションは無効です。"
-                    + "Java実行系の方でスプラッシュ画面の非表示を"
-                    + "指示してください(おそらく空の-splash:オプション)" );
-        }
-
-        if(LOADER == null){
-            LOGGER.warn(
-                    "セキュリティ設定により、"
-                    +"クラスローダを取得できませんでした");
+            LOGGER.warning(WARNMSG_SPLASH);
         }
 
         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 のスタートアップエントリ。
-     *
+     * JindolfMain のスタートアップエントリ。
      * @param args コマンドライン引数
+     * @return 起動に成功すれば0。失敗したら0以外。
      */
-    static void main(String... args){
+    public static int main(String... args){
         OptionInfo optinfo;
+        int exitCode;
 
         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;
+            STDERR.println(message);
+
+            String info =
+                    MessageFormat.format(ERRMSG_HELP, CmdOption.OPT_HELP);
+            STDERR.println(info);
+
+            exitCode = 1;
+            return exitCode;
         }
 
-        main(optinfo);
+        exitCode = main(optinfo);
 
-        return;
+        return exitCode;
     }
 
     /**
-     * JindolfOld のスタートアップエントリ。
-     *
+     * JindolfMain のスタートアップエントリ。
      * @param optinfo コマンドライン引数情報
+     * @return 起動に成功すれば0。失敗したら0以外。
      */
-    static void main(OptionInfo optinfo){
+    public static int main(OptionInfo optinfo){
+        int exitCode;
+
         if(optinfo.hasOption(CmdOption.OPT_HELP)){
             showHelpMessage();
-            System.exit(0);
-            assert false;
-            return;
+            exitCode = 0;
+            return exitCode;
         }
 
         if(optinfo.hasOption(CmdOption.OPT_VERSION)){
-            System.out.println(VerInfo.ID);
-            System.exit(0);
-            assert false;
-            return;
+            STDOUT.println(VerInfo.ID);
+            exitCode = 0;
+            return exitCode;
         }
 
         // ここ以降、アプリウィンドウの生成と表示に向けてまっしぐら。
@@ -320,40 +270,36 @@ public final class JindolfOld {
             splashWindow = showSplash();
         }
 
-        boolean hasError = false;
         try{
-            hasError = splashedMain(optinfo);
+            exitCode = splashedMain(optinfo);
         }finally{
             hideSplash(splashWindow);
         }
 
-        if(hasError) System.exit(1);
-
-        return;
+        return exitCode;
     }
 
     /**
-     * JindolfOld のスタートアップエントリ。
+     * JindolfMain のスタートアップエントリ。
      * <p>スプラッシュウィンドウが出ている状態。
+     * 時間のかかる初期化処理はなるべくこの中へ。
      * @param optinfo コマンドライン引数情報
-     * @return エラーがあればtrue
+     * @return 起動に成功すれば0。失敗したら0以外。
      */
-    static boolean splashedMain(OptionInfo optinfo){
-        final AppSetting appSetting = new AppSetting();
-        appSetting.applyOptionInfo(optinfo);
-
+    public static int splashedMain(OptionInfo optinfo){
         if(optinfo.hasOption(CmdOption.OPT_VMINFO)){
-            System.out.println(EnvInfo.getVMInfo());
+            STDOUT.println(EnvInfo.getVMInfo());
         }
 
         LogUtils.initRootLogger(optinfo.hasOption(CmdOption.OPT_CONSOLELOG));
         // ここからロギング解禁
 
-        ConfigStore configStore = appSetting.getConfigStore();
-        dumpBootInfo(optinfo, configStore);
+        final AppSetting appSetting = new AppSetting(optinfo);
+        dumpBootInfo(appSetting);
 
-        ConfigFile.setupConfigDirectory(configStore);
-        ConfigFile.setupLockFile(configStore);
+        ConfigStore configStore = appSetting.getConfigStore();
+        configStore.prepareConfigDir();
+        configStore.tryLock();
         // ここから設定格納ディレクトリ解禁
 
         appSetting.loadConfig();
@@ -364,8 +310,7 @@ public final class JindolfOld {
             @SuppressWarnings("CallToThreadYield")
             public void run(){
                 LOGGER.info("シャットダウン処理に入ります…");
-                System.out.flush();
-                System.err.flush();
+                flush();
                 runtime.gc();
                 Thread.yield();
                 runtime.runFinalization(); // 危険?
@@ -374,11 +319,9 @@ public final class JindolfOld {
             }
         });
 
-        preInitClass();
-
         LoggingDispatcher.replaceEventQueue();
 
-        boolean hasError = false;
+        int exitCode = 0;
         try{
             EventQueue.invokeAndWait(new Runnable(){
                 public void run(){
@@ -387,16 +330,16 @@ public final class JindolfOld {
                 }
             });
         }catch(InvocationTargetException e){
-            LOGGER.fatal("アプリケーション初期化に失敗しました", e);
-            e.printStackTrace(System.err);
-            hasError = true;
+            LOGGER.log(Level.SEVERE, FATALMSG_INITFAIL, e);
+            e.printStackTrace(STDERR);
+            exitCode = 1;
         }catch(InterruptedException e){
-            LOGGER.fatal("アプリケーション初期化に失敗しました", e);
-            e.printStackTrace(System.err);
-            hasError = true;
+            LOGGER.log(Level.SEVERE, FATALMSG_INITFAIL, e);
+            e.printStackTrace(STDERR);
+            exitCode = 1;
         }
 
-        return hasError;
+        return exitCode;
     }
 
     /**
@@ -404,10 +347,7 @@ public final class JindolfOld {
      * @param appSetting アプリ設定
      */
     private static void startGUI(AppSetting appSetting){
-        LandsModel model = new LandsModel();
-        model.loadLandList();
-
-        JFrame topFrame = buildMVC(appSetting, model);
+        JFrame topFrame = buildMVC(appSetting);
 
         GUIUtils.modifyWindowAttributes(topFrame, true, false, true);
 
@@ -434,17 +374,21 @@ public final class JindolfOld {
     /**
      * モデル・ビュー・コントローラの結合。
      * @param appSetting アプリ設定
-     * @param model 最上位のデータモデル
      * @return アプリケーションのトップフレーム
      */
-    private static JFrame buildMVC(AppSetting appSetting, LandsModel model){
+    private static JFrame buildMVC(AppSetting appSetting){
+        LandsModel model = new LandsModel();
+        WindowManager windowManager = new WindowManager();
         ActionManager actionManager = new ActionManager();
-        TopView topView = new TopView();
 
-        Controller controller =
-                new Controller(appSetting, actionManager, topView, model);
+        model.loadLandList();
+
+        Controller controller = new Controller(model,
+                                               windowManager,
+                                               actionManager,
+                                               appSetting );
 
-        JFrame topFrame = controller.createTopFrame();
+        JFrame topFrame = controller.getTopFrame();
 
         return topFrame;
     }
index a23ea9f..bf0629b 100644 (file)
@@ -7,6 +7,8 @@
 
 package jp.sfjp.jindolf;
 
+import java.awt.Frame;
+import java.io.PrintStream;
 import javax.swing.JOptionPane;
 
 /**
@@ -27,20 +29,25 @@ import javax.swing.JOptionPane;
  */
 public final class JreChecker {
 
-    /** required JRE version. */
+    /** Jindolfが実行時に必要とするJREの版。 */
     public static final String REQUIRED_JRE_VER = "1.5";
 
-    /** exit code. */
+    /** 互換性エラーの終了コード。 */
     public static final int EXIT_CODE_INCOMPAT_JRE = 1;
 
+    private static final PrintStream STDERR = System.err;
+
     private static final String DIALOG_TITLE =
             "JRE Incompatibility detected...";
 
     private static final int MAX_LINE = 40;
+    private static final int DEF_LINES = 5;
+    private static final int INIT_SBUF = 100;
 
 
     /**
      * 隠しコンストラクタ。
+     * <p><code>assert false;</code> 書きたいけど書いちゃだめ。
      */
     private JreChecker(){
         super();
@@ -53,16 +60,14 @@ public final class JreChecker {
      * @param klassName FQDNなクラス名
      * @return ロードできたらtrue
      */
-    private static boolean hasClass(String klassName){
-        boolean result = false;
+    public static boolean hasClass(String klassName){
+        boolean result;
 
         try{
             Class.forName(klassName); // 1.2Laterな3引数版メソッドは利用禁止
             result = true;
         }catch(ClassNotFoundException e){
             result = false;
-        }catch(LinkageError e){
-            throw e;
         }
 
         return result;
@@ -71,6 +76,7 @@ public final class JreChecker {
     /**
      * JRE 1.1 相当のランタイムライブラリが提供されているか判定する。
      * @return 提供されているならtrue
+     * @see java.io.Serializable
      */
     public static boolean has11Runtime(){
         boolean result = hasClass("java.io.Serializable");
@@ -80,6 +86,7 @@ public final class JreChecker {
     /**
      * JRE 1.2 相当のランタイムライブラリが提供されているか判定する。
      * @return 提供されているならtrue
+     * @see java.util.Iterator
      */
     public static boolean has12Runtime(){
         boolean result;
@@ -91,6 +98,7 @@ public final class JreChecker {
     /**
      * JRE 1.3 相当のランタイムライブラリが提供されているか判定する。
      * @return 提供されているならtrue
+     * @see java.util.TimerTask
      */
     public static boolean has13Runtime(){
         boolean result;
@@ -102,6 +110,7 @@ public final class JreChecker {
     /**
      * JRE 1.4 相当のランタイムライブラリが提供されているか判定する。
      * @return 提供されているならtrue
+     * @see java.lang.CharSequence
      */
     public static boolean has14Runtime(){
         boolean result;
@@ -113,6 +122,7 @@ public final class JreChecker {
     /**
      * JRE 1.5 相当のランタイムライブラリが提供されているか判定する。
      * @return 提供されているならtrue
+     * @see java.lang.Appendable
      */
     public static boolean has15Runtime(){
         boolean result;
@@ -124,6 +134,7 @@ public final class JreChecker {
     /**
      * JRE 1.6 相当のランタイムライブラリが提供されているか判定する。
      * @return 提供されているならtrue
+     * @see java.util.Deque
      */
     public static boolean has16Runtime(){
         boolean result;
@@ -132,8 +143,16 @@ public final class JreChecker {
         return result;
     }
 
+    // TODO JRE1.7,1.8 対応
+
     /**
-     * JREもしくはjava.langパッケージの仕様バージョンを返す。
+     * JREもしくは<code>java.lang</code>パッケージの
+     * 仕様バージョンを返す。
+     * <ol>
+     * <li>システムプロパティ<code>java.specification.version</code>
+     * <li>システムプロパティ<code>java.version</code>
+     * <li><code>java.lang</code>パッケージの仕様バージョン
+     * </ol>の順でバージョンが求められる。
      * @return 仕様バージョン文字列。不明ならnull
      */
     public static String getLangPkgSpec(){
@@ -162,6 +181,7 @@ public final class JreChecker {
 
     /**
      * JREのインストール情報を返す。
+     * システムプロパティ<code>java.home</code>の取得が試みられる。
      * @return インストール情報。不明ならnull
      */
     public static String getJreHome(){
@@ -172,7 +192,6 @@ public final class JreChecker {
         }catch(SecurityException e){
             result = null;
         }
-        if(result != null) return result;
 
         return result;
     }
@@ -182,7 +201,8 @@ public final class JreChecker {
      * @return エラーメッセージ
      */
     public static String buildErrMessage(){
-        StringBuffer message = new StringBuffer();
+        // このクラスではStringBuilder禁止
+        StringBuffer message = new StringBuffer(INIT_SBUF);
 
         message.append("ERROR : Java JRE ")
                .append(REQUIRED_JRE_VER)
@@ -190,10 +210,9 @@ public final class JreChecker {
 
         String specVer = getLangPkgSpec();
         if(specVer != null){
-            message.append('\n')
-                   .append("but").append('\u0020')
+            message.append("\nbut\u0020")
                    .append(specVer)
-                   .append('\u0020').append("detected.");
+                   .append("\u0020detected.");
         }
 
         String jreHome = getJreHome();
@@ -207,18 +226,19 @@ public final class JreChecker {
     }
 
     /**
-     * 指定された文字数で行の長さを揃える。
+     * æ\8c\87å®\9aã\81\95ã\82\8cã\81\9fæ\96\87å­\97æ\95°ã\81§è¡\8cã\81®é\95·ã\81\95ã\82\92æ\94¹è¡\8cæ\96\87å­\97ã\81§æ\8f\83ã\81\88ã\82\8bã\80\82
      * <p>サロゲートペアは無視される。
      * @param text 文字列
      * @param limit 行ごとの最大文字数
      * @return 改行済みの文字列
      */
     public static String alignLine(String text, int limit){
-        StringBuffer message = new StringBuffer();
+        // このクラスではStringBuilder禁止
+        int textLength = text.length();
+        StringBuffer message = new StringBuffer(textLength + DEF_LINES);
 
         int lineLength = 0;
 
-        int textLength = text.length();
         for(int idx = 0; idx < textLength; idx++){
             if(lineLength >= limit){
                 message.append('\n');
@@ -237,39 +257,37 @@ public final class JreChecker {
     }
 
     /**
+     * JRE環境をチェックする。(JRE1.5)
+     * <p>もしJREの非互換性が検出されたらエラーメッセージを報告する。
+     * @return 互換性があれば0、無ければ非0
+     */
+    public static int checkJre(){
+        if(has15Runtime()) return 0;
+
+        String message = buildErrMessage();
+        STDERR.println(message);
+        STDERR.flush();
+        if(has12Runtime()){
+            showErrorDialog(message);
+        }
+
+        return EXIT_CODE_INCOMPAT_JRE;
+    }
+
+    /**
      * Swingダイアログでエラーを報告する。
      * <p>ボタンを押すまでの間、実行はブロックされる。
-     * <p>JRE1.2環境が用意されていなければ何もしない。
      * <p>GUIに接続できなければ何か例外を投げるかもしれない。
      * @param text エラー文面
      */
     public static void showErrorDialog(String text){
-        if( ! has12Runtime() ) return;
         String aligned = alignLine(text, MAX_LINE);
+
+        Frame parent = null;
         JOptionPane.showMessageDialog(
-                null,
+                parent,
                 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;
     }
index e9d3144..381d6a8 100644 (file)
@@ -9,6 +9,7 @@ package jp.sfjp.jindolf;
 
 import java.awt.image.BufferedImage;
 import java.io.BufferedInputStream;
+import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -25,7 +26,7 @@ import javax.swing.ImageIcon;
  * <p>{@link Class}用と{@link ClassLoader}用とでは
  * 微妙に絶対リソース名の形式が異なることに留意せよ。
  * <p>基本的に、リソースファイルへのアクセスにおける異常系は
- * リカバリの対象外とする。ビルド工程の不手際扱い。
+ * リカバリの対象外とする。(ビルド工程の不手際扱い。)
  * @see java.lang.Class#getResource
  */
 public final class ResourceManager {
@@ -42,7 +43,10 @@ public final class ResourceManager {
     public static final String PKG_SEPARATOR =
             Character.toString(PKG_SEPCHAR);
 
-    /** デフォルトで用いられるルートパッケージ。 */
+    /**
+     * デフォルトで用いられるルートパッケージ。
+     * 相対リソース名の起点となる。
+     */
     public static final Package DEF_ROOT_PACKAGE;
     /** デフォルトで用いられるクラスローダ。 */
     public static final ClassLoader DEF_LOADER;
@@ -61,9 +65,7 @@ public final class ResourceManager {
      * 隠しコンストラクタ。
      */
     private ResourceManager(){
-        super();
         assert false;
-        return;
     }
 
 
@@ -98,7 +100,7 @@ public final class ResourceManager {
 
         String pkgName = pkg.getName();
         String result = pkgName.replace(PKG_SEPCHAR, RES_SEPCHAR);
-        if( ! result.isEmpty() ){
+        if(result.length() > 0){
             result += RES_SEPARATOR;
         }
 
@@ -138,8 +140,8 @@ public final class ResourceManager {
      * @return リソースのURL。リソースが見つからなければnull。
      */
     public static URL getResource(ClassLoader loader,
-                                  Package rootPkg,
-                                  String resPath ){
+                                    Package rootPkg,
+                                    String resPath ){
         String fullName;
         if(isAbsoluteResourcePath(resPath)){
             fullName = resPath.substring(1);    // chop '/' heading
@@ -173,7 +175,7 @@ public final class ResourceManager {
      * @return リソースの入力ストリーム。リソースが見つからなければnull。
      */
     public static InputStream getResourceAsStream(Package rootPkg,
-                                                  String resPath ){
+                                                     String resPath ){
         return getResourceAsStream(DEF_LOADER, rootPkg, resPath);
     }
 
@@ -187,8 +189,8 @@ public final class ResourceManager {
      * @return リソースの入力ストリーム。リソースが見つからなければnull。
      */
     public static InputStream getResourceAsStream(ClassLoader loader,
-                                                  Package rootPkg,
-                                                  String resPath ){
+                                                     Package rootPkg,
+                                                     String resPath ){
         URL url = getResource(loader, rootPkg, resPath);
         if(url == null) return null;
 
@@ -268,12 +270,13 @@ public final class ResourceManager {
      * @param resPath テキストファイルのリソース名
      * @return テキスト。リソースが読み込めなければnull。
      */
-    public static CharSequence getTextFile(String resPath){
+    public static String getTextFile(String resPath){
         InputStream is = getResourceAsStream(resPath);
         if(is == null) return null;
         is = new BufferedInputStream(is);
 
         Reader reader = new InputStreamReader(is, CS_UTF8);
+        reader = new BufferedReader(reader);
         LineNumberReader lineReader = new LineNumberReader(reader);
 
         StringBuilder result = new StringBuilder();
@@ -297,7 +300,9 @@ public final class ResourceManager {
             result = null;
         }
 
-        return result;
+        if(result == null) return null;
+
+        return result.toString();
     }
 
 }
index 7abd857..ffd003f 100644 (file)
@@ -7,6 +7,7 @@
 
 package jp.sfjp.jindolf;
 
+import java.text.MessageFormat;
 import java.util.Properties;
 
 /**
@@ -51,25 +52,25 @@ public final class VerInfo {
         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");
+        CONTACT   = getPackageInfo(verProp, PFX_CONTACT,   "Where?");
         INCEPTION = getPackageInfo(verProp, PFX_INCEPTION, "2008");
         COMMENT   = getPackageInfo(verProp, PFX_COMMENT,   "");
 
-        COPYRIGHT = "Copyright(c)" +"\u0020"+ INCEPTION +"\u0020"+ AUTHOR;
+        COPYRIGHT = MessageFormat.format(
+                "Copyright(c) {0} {1}",
+                INCEPTION, AUTHOR );
 
-        ID = TITLE
-            +"\u0020"+ "Ver." + VERSION
-            +"\u0020"+ COPYRIGHT
-            +"\u0020"+ "("+ LICENSE +")";
+        ID = MessageFormat.format(
+                "{0} Ver.{1} {2} ({3})",
+                TITLE, VERSION, COPYRIGHT, LICENSE );
     }
 
+
     /**
      * 隠しコンストラクタ。
      */
     private VerInfo(){
-        super();
         assert false;
-        return;
     }
 
 
@@ -81,8 +82,8 @@ public final class VerInfo {
      * @return パッケージ情報
      */
     static String getPackageInfo(Properties prop,
-                                 String prefix,
-                                 String defValue ){
+                                   String prefix,
+                                   String defValue ){
         String result = getPackageInfo(prop, prefix,
                                        ResourceManager.DEF_ROOT_PACKAGE,
                                        defValue );
@@ -98,9 +99,9 @@ public final class VerInfo {
      * @return パッケージ情報
      */
     static String getPackageInfo(Properties prop,
-                                 String prefix,
-                                 Package pkg,
-                                 String defValue ){
+                                   String prefix,
+                                   Package pkg,
+                                   String defValue ){
         String propKeyName = prefix + pkg.getName();
         String result = prop.getProperty(propKeyName, defValue);
 
@@ -152,23 +153,16 @@ public final class VerInfo {
     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);
+        result.append(MessageFormat.format(
+                  "{0}\u0020\u0020\u0020Version {1}\n"
+                + "{2}\n"
+                + "ライセンス: {3}\n"
+                + "連絡先: {4}",
+                TITLE, VERSION, COPYRIGHT, LICENSE, CONTACT )
+                );
 
         if(COMMENT.length() > 0){
-            result.append('\n')
-                  .append(COMMENT);
+            result.append('\n').append(COMMENT);
         }
 
         String message = result.toString();
index 39ebe77..373d209 100644 (file)
@@ -8,8 +8,10 @@
 package jp.sfjp.jindolf.config;
 
 import java.awt.Font;
+import java.awt.Rectangle;
 import java.io.File;
 import jp.sfjp.jindolf.data.DialogPref;
+import jp.sfjp.jindolf.glyph.Font2Json;
 import jp.sfjp.jindolf.glyph.FontInfo;
 import jp.sfjp.jindolf.net.ProxyInfo;
 import jp.sourceforge.jovsonz.JsBoolean;
@@ -22,22 +24,23 @@ import jp.sourceforge.jovsonz.JsValue;
  */
 public class AppSetting{
 
-    private static final String HASH_PROXY = "proxy";
+    // デフォルトのウィンドウサイズ
+    private static final int DEF_WIDTH  = 800;
+    private static final int DEF_HEIGHT = 600;
 
-    private static final String HASH_FONT = "font";
+    private static final String HASH_FONT        = "font";
     private static final String HASH_USEBODYICON = "useBodyIcon";
     private static final String HASH_USEMONOTOMB = "useMonoTomb";
-    private static final String HASH_SIMPLEMODE = "isSimpleMode";
+    private static final String HASH_SIMPLEMODE  = "isSimpleMode";
     private static final String HASH_ALIGNBALOON = "alignBaloonWidth";
+    private static final String HASH_PROXY       = "proxy";
 
-    private OptionInfo optInfo;
-    private ConfigStore configStore;
-    private FontInfo fontInfo = FontInfo.DEFAULT_FONTINFO;
 
-    private int frameWidth  = 800;
-    private int frameHeight = 600;
-    private int frameXpos = Integer.MIN_VALUE;
-    private int frameYpos = Integer.MIN_VALUE;
+    private final OptionInfo optInfo;
+    private final ConfigStore configStore;
+    private final Rectangle frameRect;
+
+    private FontInfo fontInfo;
 
     private ProxyInfo proxyInfo = ProxyInfo.DEFAULT;
 
@@ -48,104 +51,114 @@ public class AppSetting{
 
     /**
      * コンストラクタ。
+     * @param info コマンドライン引数
      */
-    public AppSetting(){
+    public AppSetting(OptionInfo info){
         super();
+
+        this.optInfo = info;
+        this.configStore = parseConfigStore(this.optInfo);
+        this.frameRect = parseGeometrySetting(this.optInfo);
+
         return;
     }
 
+
     /**
      * 設定格納ディレクトリ関係の解析。
-     * @param optionInfo コマンドライン情報
+     * @param option コマンドラインオプション情報
      * @return 設定ディレクトリ情報
      */
-    private static ConfigStore parseConfigStore(OptionInfo optionInfo){
-        CmdOption opt =
-                optionInfo.getExclusiveOption(CmdOption.OPT_CONFDIR,
-                                              CmdOption.OPT_NOCONF );
+    private static ConfigStore parseConfigStore(OptionInfo option){
+        CmdOption opt = option.getExclusiveOption(CmdOption.OPT_CONFDIR,
+                                                  CmdOption.OPT_NOCONF );
 
         boolean useConfig;
+        boolean isImplicitPath;
         File configPath;
 
         if(opt == CmdOption.OPT_NOCONF){
             useConfig = false;
+            isImplicitPath = true;
             configPath = null;
         }else if(opt == CmdOption.OPT_CONFDIR){
-            String path = optionInfo.getStringArg(CmdOption.OPT_CONFDIR);
             useConfig = true;
-            configPath = FileUtils.supplyFullPath(new File(path));
+            isImplicitPath = false;
+            String optPath = option.getStringArg(opt);
+            configPath = FileUtils.supplyFullPath(new File(optPath));
         }else{
             useConfig = true;
-            File path = ConfigFile.getImplicitConfigDirectory();
-            configPath = path;
+            isImplicitPath = true;
+            configPath = ConfigFile.getImplicitConfigDirectory();
         }
 
-        ConfigStore result = new ConfigStore(useConfig, configPath);
+        ConfigStore result =
+                new ConfigStore(useConfig, isImplicitPath, configPath);
 
         return result;
     }
 
     /**
-     * コマンドラインオプションからアプリ設定を展開する。
-     * @param optionInfo オプション情報
+     * ウィンドウジオメトリ関係の設定。
+     * @param option コマンドラインオプション情報
+     * @return ウィンドウ矩形。
      */
-    public void applyOptionInfo(OptionInfo optionInfo){
-        this.optInfo = optionInfo;
-        this.configStore = parseConfigStore(optionInfo);
-        applyFontSetting();
-        applyGeometrySetting();
-        return;
+    private static Rectangle parseGeometrySetting(OptionInfo option){
+        Rectangle result = new Rectangle(Integer.MIN_VALUE,
+                                         Integer.MIN_VALUE,
+                                         DEF_WIDTH,
+                                         DEF_HEIGHT );
+
+        Integer ival;
+
+        ival = option.initialFrameWidth();
+        if(ival != null) result.width = ival;
+
+        ival = option.initialFrameHeight();
+        if(ival != null) result.height = ival;
+
+        ival = option.initialFrameXpos();
+        if(ival != null) result.x = ival;
+
+        ival = option.initialFrameYpos();
+        if(ival != null) result.y = ival;
+
+        return result;
     }
 
+
     /**
-     * フォント関係の設定。
+     * フォントオプションの解析。
+     * @param baseFont 元のフォント設定。
+     * @return コマンドライン設定で補正されたフォント設定
      */
-    private void applyFontSetting(){
+    private FontInfo parseFontOption(FontInfo baseFont){
+        FontInfo result;
+
         String fontName = this.optInfo.getStringArg(CmdOption.OPT_INITFONT);
+        if(fontName != null){
+            Font font = Font.decode(fontName);
+            result = baseFont.deriveFont(font);
+        }else{
+            result = baseFont;
+        }
 
         Boolean useAntiAlias =
                 this.optInfo.getBooleanArg(CmdOption.OPT_ANTIALIAS);
         if(useAntiAlias == null){
-            useAntiAlias = this.fontInfo.isAntiAliased();
+            useAntiAlias = baseFont.isAntiAliased();
         }
 
         Boolean useFractional =
                 this.optInfo.getBooleanArg(CmdOption.OPT_FRACTIONAL);
         if(useFractional == null){
-            useFractional = this.fontInfo.usesFractionalMetrics();
+            useFractional = baseFont.usesFractionalMetrics();
         }
 
-        if(fontName != null){
-            Font font = Font.decode(fontName);
-            this.fontInfo = this.fontInfo.deriveFont(font);
-        }
-
-        this.fontInfo =
-                this.fontInfo.deriveRenderContext(useAntiAlias,
-                                                  useFractional );
-
-        return;
-    }
-
-    /**
-     * ジオメトリ関係の設定。
-     */
-    private void applyGeometrySetting(){
-        Integer ival;
-
-        ival = this.optInfo.initialFrameWidth();
-        if(ival != null) this.frameWidth = ival;
-
-        ival = this.optInfo.initialFrameHeight();
-        if(ival != null) this.frameHeight = ival;
-
-        ival = this.optInfo.initialFrameXpos();
-        if(ival != null) this.frameXpos = ival;
+        result = result.deriveRenderContext(useAntiAlias,
+                                            useFractional );
 
-        ival = this.optInfo.initialFrameYpos();
-        if(ival != null) this.frameYpos = ival;
-
-        return;
+        return result;
     }
 
     /**
@@ -169,7 +182,8 @@ public class AppSetting{
      * @return 初期のフレーム幅
      */
     public int initialFrameWidth(){
-        return this.frameWidth;
+        int width = this.frameRect.width;
+        return width;
     }
 
     /**
@@ -177,7 +191,8 @@ public class AppSetting{
      * @return 初期のフレーム高
      */
     public int initialFrameHeight(){
-        return this.frameHeight;
+        int height = this.frameRect.height;
+        return height;
     }
 
     /**
@@ -186,7 +201,8 @@ public class AppSetting{
      * @return 初期のフレーム位置のX座標
      */
     public int initialFrameXpos(){
-        return this.frameXpos;
+        int xPos = this.frameRect.x;
+        return xPos;
     }
 
     /**
@@ -195,7 +211,8 @@ public class AppSetting{
      * @return 初期のフレーム位置のY座標
      */
     public int initialFrameYpos(){
-        return this.frameYpos;
+        int yPos = this.frameRect.y;
+        return yPos;
     }
 
     /**
@@ -203,6 +220,9 @@ public class AppSetting{
      * @return フォント設定
      */
     public FontInfo getFontInfo(){
+        if(this.fontInfo == null){
+            this.fontInfo = parseFontOption(FontInfo.DEFAULT_FONTINFO);
+        }
         return this.fontInfo;
     }
 
@@ -281,9 +301,9 @@ public class AppSetting{
         JsValue value = root.getValue(HASH_FONT);
         if(value instanceof JsObject){
             JsObject font = (JsObject) value;
-            FontInfo info = FontInfo.decodeJson(font);
+            FontInfo info = Font2Json.decodeJson(font);
+            info = parseFontOption(info);
             setFontInfo(info);
-            applyFontSetting();
         }
 
         DialogPref pref = new DialogPref();
@@ -340,7 +360,7 @@ public class AppSetting{
 
         JsObject root = new JsObject();
 
-        JsObject font = FontInfo.buildJson(getFontInfo());
+        JsObject font = Font2Json.buildJson(getFontInfo());
         root.putValue(HASH_FONT, font);
 
         DialogPref pref = getDialogPref();
index 1a3a7b8..057988b 100644 (file)
@@ -8,6 +8,8 @@
 package jp.sfjp.jindolf.config;
 
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.EnumSet;
 import java.util.List;
 import jp.sfjp.jindolf.ResourceManager;
 
@@ -43,6 +45,22 @@ public enum CmdOption {
     ;
 
 
+    private static final Collection<CmdOption> OPTS_INDEPENDENT =
+            EnumSet.of(
+            OPT_HELP,
+            OPT_VERSION,
+            OPT_VMINFO,
+            OPT_BOLDMETAL,
+            OPT_NOSPLASH,
+            OPT_CONSOLELOG,
+            OPT_NOCONF
+            );
+    private static final Collection<CmdOption> OPTS_BOOLEAN =
+            EnumSet.of(
+            OPT_ANTIALIAS,
+            OPT_FRACTIONAL
+            );
+
     private static final String RES_HELPTEXT = "resources/help.txt";
 
 
@@ -51,7 +69,7 @@ public enum CmdOption {
 
     /**
      * コンストラクタ。
-     * @param names 頭のハイフンを除いたオプション名の一覧
+     * @param names オプション名の一覧
      */
     private CmdOption(String ... names){
         assert names.length > 0;
@@ -64,10 +82,8 @@ public enum CmdOption {
      * ヘルプメッセージ(オプションの説明)を返す。
      * @return ヘルプメッセージ
      */
-    public static CharSequence getHelpText(){
-        CharSequence helpText =
-            ResourceManager.getTextFile(RES_HELPTEXT);
-
+    public static String getHelpText(){
+        String helpText = ResourceManager.getTextFile(RES_HELPTEXT);
         return helpText;
     }
 
@@ -101,19 +117,7 @@ public enum CmdOption {
      * @return 単体で意味をなすならtrue
      */
     public boolean isIndepOption(){
-        switch(this){
-        case OPT_HELP:
-        case OPT_VERSION:
-        case OPT_VMINFO:
-        case OPT_BOLDMETAL:
-        case OPT_NOSPLASH:
-        case OPT_CONSOLELOG:
-        case OPT_NOCONF:
-            return true;
-        default:
-            break;
-        }
-
+        if(OPTS_INDEPENDENT.contains(this)) return true;
         return false;
     }
 
@@ -122,25 +126,19 @@ public enum CmdOption {
      * @return 真偽指定を一つ必要とするオプションならtrue
      */
     public boolean isBooleanOption(){
-        switch(this){
-        case OPT_ANTIALIAS:
-        case OPT_FRACTIONAL:
-            return true;
-        default:
-            break;
-        }
-
+        if(OPTS_BOOLEAN.contains(this)) return true;
         return false;
     }
 
     /**
-     * 頭のハイフンを除いたオプション名を返す。
-     * ã\82ªã\83\97ã\82·ã\83§ã\83³å\90\8dã\81\8cè¤\87æ\95°æ\8c\87å®\9aã\81\95ã\82\8cã\81¦ã\81\84ã\81\9f場合は最初のオプション名
+     * オプション名を返す。
+     * ã\82ªã\83\97ã\82·ã\83§ã\83³å\88¥å\90\8dã\81\8cè¤\87æ\95°æ\8c\87å®\9aã\81\95ã\82\8cã\81¦ã\81\84ã\82\8b場合は最初のオプション名
      * @return オプション名
      */
     @Override
     public String toString(){
-        return this.nameList.get(0);
+        String result = this.nameList.get(0);
+        return result;
     }
 
 }
index 9fc38af..541b870 100644 (file)
@@ -18,7 +18,6 @@ import java.nio.charset.Charset;
 import javax.swing.JDialog;
 import javax.swing.JOptionPane;
 import jp.sfjp.jindolf.VerInfo;
-import jp.sfjp.jindolf.log.LogWrapper;
 import jp.sfjp.jindolf.view.LockErrorPane;
 
 /**
@@ -33,7 +32,6 @@ public final class ConfigFile{
     private static final String JINCONF_DOT = ".jindolf";
     private static final String FILE_README = "README.txt";
     private static final Charset CHARSET_README = Charset.forName("UTF-8");
-    private static final Charset CHARSET_JSON = Charset.forName("UTF-8");
 
     private static final String MSG_POST =
             "<ul>"
@@ -45,8 +43,6 @@ public final class ConfigFile{
             + "設定格納ディレクトリを使わずに起動することができます。<br>"
             + "</ul>";
 
-    private static final LogWrapper LOGGER = new LogWrapper();
-
 
     /**
      * 隠れコンストラクタ。
@@ -58,62 +54,6 @@ public final class ConfigFile{
 
 
     /**
-     * 設定格納ディレクトリのセットアップ。
-     * @param configStore 設定ディレクトリ
-     * @return 設定格納ディレクトリ
-     */
-    public static File setupConfigDirectory(ConfigStore configStore){
-        File configPath;
-
-        if( ! configStore.useStoreFile() ){
-            configPath = null;
-        }else{
-            String optName;
-            if(configStore.getConfigPath() != null){
-                configPath = configStore.getConfigPath();
-                optName = CmdOption.OPT_CONFDIR.toString();
-            }else{
-                configPath = ConfigFile.getImplicitConfigDirectory();
-                optName = null;
-            }
-            if( ! configPath.exists() ){
-                configPath =
-                        ConfigFile.buildConfigDirectory(configPath, optName);
-            }
-            ConfigFile.checkAccessibility(configPath);
-        }
-
-        configStore.setConfigPath(configPath);
-
-        return configPath;
-    }
-
-    /**
-     * ロックファイルのセットアップ。
-     * @param configStore 設定ディレクトリ
-     * @return ロックオブジェクト
-     */
-    public static InterVMLock setupLockFile(ConfigStore configStore){
-        File configPath = configStore.getConfigPath();
-        if(configPath == null) return null;
-
-        File lockFile = new File(configPath, "lock");
-        InterVMLock lock = new InterVMLock(lockFile);
-
-        lock.tryLock();
-
-        if( ! lock.isFileOwner() ){
-            confirmLockError(lock);
-            if( ! lock.isFileOwner() ){
-                configStore.setConfigPath(null);
-                configStore.setUseStoreFile(false);
-            }
-        }
-
-        return lock;
-    }
-
-    /**
      * 暗黙的な設定格納ディレクトリを返す。
      * 起動元JARファイルと同じディレクトリに、
      * アクセス可能なディレクトリ"Jindolf"が
@@ -153,13 +93,12 @@ public final class ConfigFile{
      * まだ存在しない設定格納ディレクトリを新規に作成する。
      * エラーがあればダイアログ提示とともにVM終了する。
      * @param confPath 設定格納ディレクトリ
-     * @param optName 設定を指定したオプション名。
-     * 暗黙的に指示されたものならnullを渡すべし。
+     * @param isImplicitPath ディレクトリが暗黙的に指定されたものならtrue。
      * @return 新規に作成した設定格納ディレクトリ
      * @throws IllegalArgumentException すでにそのディレクトリは存在する。
      */
     public static File buildConfigDirectory(File confPath,
-                                               String optName)
+                                               boolean isImplicitPath )
             throws IllegalArgumentException{
         if(confPath.exists()) throw new IllegalArgumentException();
 
@@ -169,9 +108,11 @@ public final class ConfigFile{
                 "設定格納ディレクトリ<br>"
                 + getCenteredFileName(absPath)
                 + "の作成に失敗しました。";
-        if(optName != null){
+        if( ! isImplicitPath ){
             preErrMessage =
-                    "<code>" + optName + "</code>&nbsp;オプション"
+                    "<code>"
+                    + CmdOption.OPT_CONFDIR
+                    + "</code>&nbsp;オプション"
                     + "で指定された、<br>"
                     + preErrMessage;
         }
index beecf80..d00e4bb 100644 (file)
@@ -23,7 +23,8 @@ import java.io.OutputStreamWriter;
 import java.io.Reader;
 import java.io.Writer;
 import java.nio.charset.Charset;
-import jp.sfjp.jindolf.log.LogWrapper;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import jp.sourceforge.jovsonz.JsComposition;
 import jp.sourceforge.jovsonz.JsObject;
 import jp.sourceforge.jovsonz.JsParseException;
@@ -45,12 +46,15 @@ public class ConfigStore {
     /** 台詞表示設定ファイル。 */
     public static final File TALKCONFIG_FILE = new File("talkconfig.json");
 
+    private static final String LOCKFILE = "lock";
+
     private static final Charset CHARSET_JSON = Charset.forName("UTF-8");
 
-    private static final LogWrapper LOGGER = new LogWrapper();
+    private static final Logger LOGGER = Logger.getAnonymousLogger();
 
 
     private boolean useStoreFile;
+    private boolean isImplicitPath;
     private File configPath;
 
 
@@ -60,18 +64,41 @@ public class ConfigStore {
      * @param configPath 設定ディレクトリ。
      * 設定ディレクトリを使わない場合は無視され、nullとして扱われる。
      */
-    public ConfigStore(boolean useStoreFile, File configPath){
+    public ConfigStore(boolean useStoreFile, File configPath ){
+        this(useStoreFile, true, configPath);
+        return;
+    }
+
+    /**
+     * コンストラクタ。
+     * @param useStoreFile 設定ディレクトリへの永続化機能を使うならtrue
+     * @param isImplicitPath コマンドラインで指定されたディレクトリならfalse
+     * @param configPath 設定ディレクトリ。
+     * 設定ディレクトリを使わない場合は無視され、nullとして扱われる。
+     */
+    public ConfigStore(boolean useStoreFile,
+                         boolean isImplicitPath,
+                         File configPath ){
         super();
 
         this.useStoreFile = useStoreFile;
 
-        File path = null;
-        if(this.useStoreFile) path = configPath;
-        this.configPath = path;
+        if(this.useStoreFile){
+            this.isImplicitPath = isImplicitPath;
+        }else{
+            this.isImplicitPath = true;
+        }
+
+        if(this.useStoreFile){
+            this.configPath = configPath;
+        }else{
+            this.configPath = null;
+        }
 
         return;
     }
 
+
     /**
      * 設定ディレクトリを使うか否か判定する。
      * @return 設定ディレクトリを使うならtrue。
@@ -81,18 +108,6 @@ public class ConfigStore {
     }
 
     /**
-     * 設定ディレクトリ利用の有無を変更する。
-     * @param sw 利用するならtrue
-     */
-    public void setUseStoreFile(boolean sw){
-        this.useStoreFile = sw;
-        if( ! this.useStoreFile ){
-            this.configPath = null;
-        }
-        return;
-    }
-
-    /**
      * 設定ディレクトリを返す。
      * @return 設定ディレクトリ。設定ディレクトリを使わない場合はnull
      */
@@ -104,11 +119,43 @@ public class ConfigStore {
     }
 
     /**
-     * è¨­å®\9aã\83\87ã\82£ã\83¬ã\82¯ã\83\88ã\83ªã\82\92å¤\89æ\9b´ã\81\99る。
-     * @param path 設定ディレクトリ
+     * è¨­å®\9aã\83\87ã\82£ã\83¬ã\82¯ã\83\88ã\83ªã\81®å­\98å\9c¨ã\82\92確èª\8dã\81\97ã\80\81ã\81ªã\81\91ã\82\8cã\81°ä½\9cる。
+     * <p>設定ディレクトリを使わない場合は何もしない。
      */
-    public void setConfigPath(File path){
-        this.configPath = path;
+    public void prepareConfigDir(){
+        if( ! this.useStoreFile ) return;
+
+        if( ! this.configPath.exists() ){
+            File created =
+                ConfigFile.buildConfigDirectory(this.configPath,
+                                                this.isImplicitPath );
+            ConfigFile.checkAccessibility(created);
+        }else{
+            ConfigFile.checkAccessibility(this.configPath);
+        }
+
+        return;
+    }
+
+    /**
+     * ロックファイルの取得を試みる。
+     */
+    public void tryLock(){
+        if( ! this.useStoreFile ) return;
+
+        File lockFile = new File(this.configPath, LOCKFILE);
+        InterVMLock lock = new InterVMLock(lockFile);
+
+        lock.tryLock();
+
+        if( ! lock.isFileOwner() ){
+            ConfigFile.confirmLockError(lock);
+            if( ! lock.isFileOwner() ){
+                this.useStoreFile = false;
+                this.configPath = null;
+            }
+        }
+
         return;
     }
 
@@ -163,13 +210,13 @@ public class ConfigStore {
         try{
             root = loadJson(istream);
         }catch(IOException e){
-            LOGGER.fatal(
+            LOGGER.log(Level.SEVERE,
                     "JSONファイル["
                     + absPath
                     + "]の読み込み時に支障がありました。", e);
             return null;
         }catch(JsParseException e){
-            LOGGER.fatal(
+            LOGGER.log(Level.SEVERE,
                     "JSONファイル["
                     + absPath
                     + "]の内容に不備があります。", e);
@@ -178,7 +225,7 @@ public class ConfigStore {
             try{
                 istream.close();
             }catch(IOException e){
-                LOGGER.fatal(
+                LOGGER.log(Level.SEVERE,
                         "JSONファイル["
                         + absPath
                         + "]を閉じることができません。", e);
@@ -236,7 +283,7 @@ public class ConfigStore {
         try{
             if(absFile.createNewFile() != true) return false;
         }catch(IOException e){
-            LOGGER.fatal(
+            LOGGER.log(Level.SEVERE,
                     "JSONファイル["
                     + absPath
                     + "]の新規生成ができません。", e);
@@ -255,13 +302,13 @@ public class ConfigStore {
         try{
             saveJson(ostream, root);
         }catch(JsVisitException e){
-            LOGGER.fatal(
+            LOGGER.log(Level.SEVERE,
                     "JSONファイル["
                     + absPath
                     + "]の出力処理で支障がありました。", e);
             return false;
         }catch(IOException e){
-            LOGGER.fatal(
+            LOGGER.log(Level.SEVERE,
                     "JSONファイル["
                     + absPath
                     + "]の書き込み時に支障がありました。", e);
@@ -270,7 +317,7 @@ public class ConfigStore {
             try{
                 ostream.close();
             }catch(IOException e){
-                LOGGER.fatal(
+                LOGGER.log(Level.SEVERE,
                         "JSONファイル["
                         + absPath
                         + "]を閉じることができません。", e);
index 7d523aa..798d070 100644 (file)
@@ -26,11 +26,14 @@ public final class FileUtils{
 
     private static final Class<?> THISKLASS = FileUtils.class;
 
+    private static final String LANG_JA = "ja";
+    private static final String SYSPROP_OSNAME = "os.name";
     private static final String SCHEME_FILE = "file";
+    private static final String ENTITY_YEN = "&yen;";
 
-    /** JRE1.6のjava.io.File#setReadableに相当。 */
+    /** JRE1.6の{@link java.io.File#setReadable(boolean,boolean)}に相当。 */
     private static final Method METHOD_SETREADABLE;
-    /** JRE1.6のjava.io.File#setWritableに相当。 */
+    /** JRE1.6の{@link java.io.File#setWritable(boolean,boolean)}に相当。 */
     private static final Method METHOD_SETWRITABLE;
     /** Locale.ROOT代替品。 */
     private static final Locale ROOT = new Locale("", "", "");
@@ -83,33 +86,54 @@ public final class FileUtils{
 
 
     /**
-     * なるべく自分にだけ許可を与え自分以外には許可を与えないように
-     * ファイル属性を操作する。
-     * @param method setReadableかsetWritableのいずれかのメソッド。
-     * nullならなにもしない。
-     * @param file 操作対象のファイル。
-     * @return 成功すればtrue
-     * @throws SecurityException セキュリティ上の許可が無い場合
+     * パーミッション操作メソッドの下請け。
+     * リフレクション機構に関する異常系を吸収する。
+     * @param method setReadableかsetWritableのいずれかの2引数版メソッド。
+     * @param file 捜査対象のファイル
+     * @param flag 操作フラグ
+     * @param ownerOnly 対象は所有者のみか否か
+     * @return メソッドの戻り値。成功すればtrue
+     * @throws InvocationTargetException メソッドが発した異常系
      */
-    private static boolean invokeOwnerOnly(Method method, File file)
-            throws SecurityException{
-        if(method == null) return false;
-        if(file == null) throw new NullPointerException();
-
-        Object result1;
-        Object result2;
+    private static boolean reflectFilePermOp(
+            Method method, File file, boolean flag, boolean ownerOnly)
+            throws InvocationTargetException {
+        Object result;
         try{
-            result1 = method.invoke(file, false, false);
-            result2 = method.invoke(file, true,  true);
+            result = method.invoke(file, flag, ownerOnly);
         }catch(IllegalAccessException e){
             assert false;
-            return false;
+            throw new AssertionError(e);
         }catch(IllegalArgumentException e){
             assert false;
-            return false;
+            throw new AssertionError(e);
         }catch(ExceptionInInitializerError e){
             assert false;
-            return false;
+            throw new AssertionError(e);
+        }
+
+        assert result instanceof Boolean;
+        Boolean boolResult = (Boolean) result;
+
+        return boolResult;
+    }
+
+    /**
+     * パーミッション操作メソッドの下請け。
+     * @param method setReadableかsetWritableのいずれかの2引数版メソッド。
+     * @param file 操作対象のファイル。
+     * @param flag 操作フラグ
+     * @param ownerOnly 対象は所有者のみか否か
+     * @return 成功すればtrue
+     * @throws SecurityException セキュリティ上の許可が無い場合
+     */
+    private static boolean invokeFilePermOp(
+            Method method, File file, boolean flag, boolean ownerOnly)
+            throws SecurityException{
+        boolean result;
+
+        try{
+            result = reflectFilePermOp(method, file, flag, ownerOnly);
         }catch(InvocationTargetException e){
             Throwable cause = e.getCause();
             if(cause instanceof SecurityException){
@@ -118,18 +142,57 @@ public final class FileUtils{
                 throw (RuntimeException) cause;
             }else if(cause instanceof Error){
                 throw (Error) cause;
-            }else{
-                assert false;
             }
-            return false;
+
+            assert false;
+            throw new AssertionError(e);
         }
 
-        assert result1 instanceof Boolean;
-        assert result2 instanceof Boolean;
-        Boolean bresult1 = (Boolean) result1;
-        Boolean bresult2 = (Boolean) result2;
+        return result;
+    }
 
-        return bresult1 && bresult2;
+    /**
+     * ファイルの読み込みパーミッションを操作する。
+     * JRE1.6の{@link java.io.File#setReadable(boolean,boolean)}
+     * の代用品。
+     * <p>JRE1.6でなければなにもせずにfalseを返す。
+     * @param file ファイルorディレクトリ
+     * @param flag 操作フラグ
+     * @param ownerOnly 対象は所有者のみか否か
+     * @return 成功すればtrue
+     * @throws SecurityException セキュリティ上の許可が無い場合
+     */
+    public static boolean setReadable(
+            File file, boolean flag, boolean ownerOnly )
+            throws SecurityException {
+        if(file == null) throw new NullPointerException();
+        if(METHOD_SETREADABLE == null) return false;
+
+        boolean result;
+        result = invokeFilePermOp(METHOD_SETREADABLE, file, flag, ownerOnly);
+        return result;
+    }
+
+    /**
+     * ファイルの書き込みパーミッションを操作する。
+     * JRE1.6の{@link java.io.File#setWritable(boolean,boolean)}
+     * の代用品。
+     * <p>JRE1.6でなければなにもせずにfalseを返す。
+     * @param file ファイルorディレクトリ
+     * @param flag 操作フラグ
+     * @param ownerOnly 対象は所有者のみか否か
+     * @return 成功すればtrue
+     * @throws SecurityException セキュリティ上の許可が無い場合
+     */
+    public static boolean setWritable(
+            File file, boolean flag, boolean ownerOnly )
+            throws SecurityException {
+        if(file == null) throw new NullPointerException();
+        if(METHOD_SETWRITABLE == null) return false;
+
+        boolean result;
+        result = invokeFilePermOp(METHOD_SETWRITABLE, file, flag, ownerOnly);
+        return result;
     }
 
     /**
@@ -143,9 +206,15 @@ public final class FileUtils{
      */
     public static boolean setOwnerOnlyAccess(File file)
             throws SecurityException{
-        boolean readresult  = invokeOwnerOnly(METHOD_SETREADABLE, file);
-        boolean writeresult = invokeOwnerOnly(METHOD_SETWRITABLE, file);
-        return readresult & writeresult;
+        boolean result = true;
+
+        result &= setReadable(file, false, false);
+        result &= setReadable(file, true,  true);
+
+        result &= setWritable(file, false, false);
+        result &= setWritable(file, true, true);
+
+        return result;
     }
 
     /**
@@ -199,10 +268,12 @@ public final class FileUtils{
     /**
      * 任意のディレクトリがアクセス可能な状態にあるか判定する。
      * アクセス可能の条件を満たすためには、与えられたパスが
-     * 存在し、
-     * かつディレクトリであり、
-     * かつ読み込み可能であり、
-     * かつ書き込み可能
+     * <ul>
+     * <li>存在し、
+     * <li>かつディレクトリであり、
+     * <li>かつ読み込み可能であり、
+     * <li>かつ書き込み可能
+     * </ul>
      * でなければならない。
      * @param path 任意のディレクトリ
      * @return アクセス可能ならtrue
@@ -210,12 +281,14 @@ public final class FileUtils{
     public static boolean isAccessibleDirectory(File path){
         if(path == null) return false;
 
-        if( ! path.exists() )      return false;
-        if( ! path.isDirectory() ) return false;
-        if( ! path.canRead() )     return false;
-        if( ! path.canWrite() )    return false;
+        boolean result = true;
 
-        return true;
+        if     ( ! path.exists() )      result = false;
+        else if( ! path.isDirectory() ) result = false;
+        else if( ! path.canRead() )     result = false;
+        else if( ! path.canWrite() )    result = false;
+
+        return result;
     }
 
     /**
@@ -324,7 +397,7 @@ public final class FileUtils{
 
         String osName;
         try{
-            osName = System.getProperty("os.name");
+            osName = System.getProperty(SYSPROP_OSNAME);
         }catch(SecurityException e){
             return false;
         }
@@ -349,7 +422,7 @@ public final class FileUtils{
 
         String osName;
         try{
-            osName = System.getProperty("os.name");
+            osName = System.getProperty(SYSPROP_OSNAME);
         }catch(SecurityException e){
             return false;
         }
@@ -368,6 +441,9 @@ public final class FileUtils{
     /**
      * アプリケーション設定ディレクトリを返す。
      * 存在の有無、アクセスの可否は関知しない。
+     * <p>WindowsやLinuxではホームディレクトリ。
+     * Mac OS X ではさらにホームディレクトリの下の
+     * "Library/Application Support/"
      * @return アプリケーション設定ディレクトリ
      */
     public static File getAppSetDir(){
@@ -398,8 +474,8 @@ public final class FileUtils{
         Locale locale = Locale.getDefault();
         String lang = locale.getLanguage();
 
-        if( FileUtils.isWindowsOSFs() && lang.equals("ja") ){
-            pathName = pathName.replace(File.separator, "&yen;");
+        if( FileUtils.isWindowsOSFs() && lang.equals(LANG_JA) ){
+            pathName = pathName.replace(File.separator, ENTITY_YEN);
         }
 
         return "<code>" + pathName + "</code>";
index a45efb6..4eb8426 100644 (file)
@@ -8,35 +8,46 @@
 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.util.Collection;
 import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
+import java.util.LinkedList;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * ロックファイルを用いたVM間ロックオブジェクト。
- * 大昔のNFSではうまく動かないかも。
- * 一度でもロックに成功したロックファイルはVM終了時に消されてしまうので注意。
+ * <p>大昔のNFSではうまく動かないかも。
+ * <p>一度でもロックに成功したロックファイルは、
+ * VM終了時に消されてしまうので注意。
  */
 public class InterVMLock{
 
     /** 所持するロックオブジェクト一覧。 */
-    private static final Set<InterVMLock> ownedLockSet =
-            Collections.synchronizedSet(new HashSet<InterVMLock>());
+    private static final Collection<InterVMLock> OWNEDLOCKSET =
+            Collections.synchronizedCollection(
+                new LinkedList<InterVMLock>()
+            );
+    private static final AtomicBoolean SHUTDOWNGOING =
+            new AtomicBoolean(false);
 
     static{
         Runtime runtime = Runtime.getRuntime();
         runtime.addShutdownHook(new Thread(){
-            @Override public void run(){ clearOwnedLockSet(); }
+            @Override
+            public void run(){
+                shutdown();
+            }
         });
     }
 
 
     private final File lockFile;
     private boolean isFileOwner = false;
-    private FileOutputStream stream = null;
+    private InputStream stream = null;
+    private final Object thisLock = new Object();
 
 
     /**
@@ -53,54 +64,37 @@ public class InterVMLock{
 
 
     /**
-     * 所持するロックオブジェクト一覧への登録。
-     * @param lock 登録するロックオブジェクト
+     * 所持するロックオブジェクトすべてを解放しロックファイルを削除する。
      */
-    private static void addOwnedLock(InterVMLock lock){
-        synchronized(ownedLockSet){
-            if( ! lock.isFileOwner() ) return;
-            ownedLockSet.add(lock);
-            lock.getLockFile().deleteOnExit();
-        }
-        return;
-    }
+    private static void shutdown(){
+        if( ! SHUTDOWNGOING.compareAndSet(false, true) ) return;
 
-    /**
-     * 所持するロックオブジェクト一覧からの脱退。
-     * @param lock 脱退対象のロックオブジェクト
-     */
-    private static void removeOwnedLock(InterVMLock lock){
-        synchronized(ownedLockSet){
-            ownedLockSet.remove(lock);
+        synchronized(OWNEDLOCKSET){
+            for(InterVMLock lock : OWNEDLOCKSET){
+                lock.releaseImpl();
+            }
+            OWNEDLOCKSET.clear();
         }
         return;
     }
 
     /**
-     * 所持するロックオブジェクトすべてを解放しロックファイルを削除する。
+     * シャットダウン処理進行中or完了済みか否か判定する。
+     * @return 進行中or完了済みならtrue
      */
-    private static void clearOwnedLockSet(){
-        synchronized(ownedLockSet){
-            for(InterVMLock lock : ownedLockSet){
-                if( ! lock.isFileOwner() ) continue;
-                lock.release();
-                try{
-                    lock.getLockFile().delete();
-                }catch(SecurityException e){
-                    // NOTHING
-                }
-            }
-            ownedLockSet.clear();
-        }
-        return;
+    protected static boolean isShutdownGoing(){
+        boolean going = SHUTDOWNGOING.get();
+        return going;
     }
 
+
     /**
-     * ã\83­ã\83\83ã\82¯å¯¾è±¡ã\81®ã\83\95ã\82¡ã\82¤ã\83«ã\82\92è¿\94ã\81\99
-     * @return ロック対象ファイル
+     * ã\81\93ã\81®ã\82ªã\83\96ã\82¸ã\82§ã\82¯ã\83\88ã\81\8cã\83­ã\83\83ã\82¯ã\83\95ã\82¡ã\82¤ã\83«ã\81®ä½\9cè\80\85ã\81§ã\81\82ã\82\8bã\81\8bå\88¤å®\9aã\81\99ã\82\8b
+     * @return 作者ならtrue
      */
-    public File getLockFile(){
-        return this.lockFile;
+    public boolean isFileOwner(){
+        boolean result = this.isFileOwner;
+        return result;
     }
 
     /**
@@ -115,124 +109,185 @@ public class InterVMLock{
     }
 
     /**
-     * このオブジェクトがロックファイルの作者であるか判定する。
-     * @return 作者ならtrue
+     * ロック対象のファイルを返す。
+     * <p>勝手に作ったり消したりしないように。
+     * @return ロック対象ファイル
      */
-    public synchronized boolean isFileOwner(){
-        return this.isFileOwner;
+    public File getLockFile(){
+        return this.lockFile;
     }
 
     /**
      * ロックファイルのオープン中のストリームを返す。
      * ※ 排他制御目的のリソースなので、
-     * 勝手に書き込んだりクローズしたりしないように。
+     * 勝手に読み込んだりクローズしたりしないように。
      * @return オープン中のストリーム。オープンしてなければnull
      */
-    protected synchronized FileOutputStream getOpenedStream(){
-        if(isFileOwner()) return this.stream;
-        return null;
+    protected InputStream getOpenedStream(){
+        InputStream result = null;
+
+        synchronized(this.thisLock){
+            if(this.isFileOwner){
+                result = this.stream;
+            }
+        }
+
+        return result;
     }
 
     /**
      * ロックファイルの強制削除を試みる。
      * @return 強制削除に成功すればtrue
      */
-    public synchronized boolean forceRemove(){
-        if(isFileOwner()) release();
+    public boolean forceRemove(){
+        synchronized(this.thisLock){
+            if(this.isFileOwner) release();
 
-        if( ! isExistsFile() ) return true;
+            if( ! isExistsFile() ) return true;
 
-        try{
-            boolean result = this.lockFile.delete();
-            if( ! result ) return false;
-        }catch(SecurityException e){
-            return false;
-        }
+            try{
+                boolean result = this.lockFile.delete();
+                if( ! result ) return false;
+            }catch(SecurityException e){
+                return false;
+            }
 
-        if(isExistsFile()) return false;
+            if(isExistsFile()) return false;
+        }
 
         return true;
     }
 
     /**
-     * ロックファイルを生成する。
+     * ロックを試みる。
+     * このメソッドは実行をブロックしない。
+     * @return すでにロック済みもしくはロックに成功すればtrue
+     */
+    public boolean tryLock(){
+        if(isShutdownGoing()) return false;
+
+        synchronized(this.thisLock){
+            if(hasLockedByMe()) return true;
+            if(touchLockFile()) return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * 自身によるロックに成功しているか判定する。
+     * @return 自身によるロック中であればtrue
+     */
+    public boolean hasLockedByMe(){
+        boolean result;
+        synchronized(this.thisLock){
+            if( ! this.isFileOwner ){
+                result = false;
+            }else if( ! this.lockFile.exists() ){
+                this.isFileOwner = false;
+                result = false;
+            }else{
+                result = true;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * ロックファイルを生成する。{@link #tryLock()}の下請け。
      * 生成されるロックファイルはVM終了時に削除されるよう登録される。
      * このメソッド実行中にVM終了が重なると、
      * ロックファイルが正しく削除されない場合がありうる。
      * @return 成功すればtrue
      */
-    protected synchronized boolean touchLockFile(){
-        boolean result = false;
-        try{
-            result = this.lockFile.createNewFile();
-        }catch(IOException e){
-            // NOTHING
-        }catch(SecurityException e){
-            // NOTHING
-        }
-        if(result == false){
-            return false;
-        }
+    protected boolean touchLockFile(){
+        synchronized(this.thisLock){
+            boolean created = false;
+            try{
+                created = this.lockFile.createNewFile();
+            }catch(IOException e){
+                assert true;   // IGNORE
+            }catch(SecurityException e){
+                assert true;   // IGNORE
+            }finally{
+                if(created){
+                    this.isFileOwner = true;
+                    this.lockFile.deleteOnExit();
+                }else{
+                    this.isFileOwner = false;
+                }
+            }
+
+            if( ! created )  return false;
 
-        try{
-            this.isFileOwner = true;
-            this.stream = new FileOutputStream(this.lockFile);
-        }catch(FileNotFoundException e){
-            assert false;
-            this.isFileOwner = false;
-            this.stream = null;
             try{
-                this.lockFile.delete();
-            }catch(SecurityException e2){
-                // NOTHING
+                this.stream = new FileInputStream(this.lockFile);
+            }catch(FileNotFoundException e){
+                this.isFileOwner = false;
+                this.stream = null;
+                try{
+                    this.lockFile.delete();
+                }catch(SecurityException e2){
+                    assert true; // IGNORE
+                }
+                return false;
             }
-            return false;
-        }
 
-        addOwnedLock(this);
+            synchronized(OWNEDLOCKSET){
+                OWNEDLOCKSET.add(this);
+            }
+        }
 
         return true;
     }
 
     /**
-     * ロックを試みる。
-     * このメソッドはブロックしない。
-     * @return すでにロック済みもしくはロックに成功すればtrue
+     * ロックを解除する。
+     * <p>自分が作者であるロックファイルは閉じられ削除される。
+     * <p>削除に失敗しても無視。
+     * <p>シャットダウン処理進行中の場合は何もしない。
      */
-    public synchronized boolean tryLock(){
-        if( isFileOwner() ) return true;
+    public void release(){
+        if(isShutdownGoing()) return;
 
-        if(isExistsFile()) return false;
-        if(touchLockFile() != true) return false;
+        releaseImpl();
 
-        return true;
+        synchronized(OWNEDLOCKSET){
+            OWNEDLOCKSET.remove(this);
+        }
+
+        return;
     }
 
     /**
-     * ロックを解除する。
-     * 自分が作者であるロックファイルは閉じられ削除される。
-     * 削除に失敗しても無視。
+     * ロックを解除する。{@link #release()}の下請け。
+     * <p>自分が作者であるロックファイルは閉じられ削除される。
+     * <p>削除に失敗しても無視。
+     * <p>シャットダウン処理進行中か否かは無視される。
      */
-    public synchronized void release(){
-        if( ! isFileOwner() ) return;
-
-        try{
-            this.stream.close();
-        }catch(IOException e){
-            // NOTHING
-        }finally{
-            this.stream = null;
+    protected void releaseImpl(){
+        synchronized(this.thisLock){
+            if( ! this.isFileOwner ) return;
+
             try{
-                this.lockFile.delete();
-            }catch(SecurityException e){
-                // NOTHING
+                this.stream.close();
+            }catch(IOException e){
+                assert true;   // IGNORE
             }finally{
-                removeOwnedLock(this);
-                this.isFileOwner = false;
+                this.stream = null;
+                try{
+                    this.lockFile.delete();
+                }catch(SecurityException e){
+                    assert true;   // IGNORE
+                }finally{
+                    this.isFileOwner = false;
+                }
             }
         }
 
         return;
     }
 
+    // TODO {@link java.nio.channels.FileChannnel}によるロック機構の併用。
+
 }
index cf9211b..715d9d7 100644 (file)
@@ -23,11 +23,17 @@ import java.util.regex.Pattern;
  */
 public class OptionInfo{
 
+    private static final String REGEX_DIMNO =
+            "([1-9][0-9]{0,5})";
+    private static final String REGEX_SIGN =
+            "(?:\\+|(\\-))";
+    private static final String REGEX_LOCNO =
+            REGEX_SIGN + REGEX_DIMNO;
+    private static final String REGEX_GEOMETRY =
+              REGEX_DIMNO + "x" + REGEX_DIMNO
+            + "(?:" + REGEX_LOCNO + REGEX_LOCNO + ")?";
     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]*))?"
-                );
+            Pattern.compile(REGEX_GEOMETRY);
 
     private static final String ERRFORM_UKNOWN =
             "未定義の起動オプション[{0}]が指定されました。";
@@ -45,8 +51,8 @@ public class OptionInfo{
 
     private Integer frameWidth  = null;
     private Integer frameHeight = null;
-    private Integer frameXpos = null;
-    private Integer frameYpos = 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>();
@@ -85,12 +91,12 @@ public class OptionInfo{
      * @param option オプション種別
      * @param optTxt オプション名文字列
      * @param onoff オプション引数
-     * @throws IllegalArgumentException 構文エラー
+     * @throws IllegalArgumentException 引数の構文エラー
      */
     private static void parseBooleanSwitch(OptionInfo info,
-                                           CmdOption option,
-                                           String optTxt,
-                                           String onoff )
+                                              CmdOption option,
+                                              String optTxt,
+                                              String onoff )
             throws IllegalArgumentException{
         Boolean flag;
 
@@ -110,28 +116,16 @@ public class OptionInfo{
     }
 
     /**
-     * 文字列がマイナス記号で始まるか判定する。
-     * @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 構文エラー
+     * @throws IllegalArgumentException 引数の構文エラー
      */
     private static void parseGeometry(OptionInfo info,
-                                      String optTxt,
-                                      String geometry )
+                                        String optTxt,
+                                        String geometry )
             throws IllegalArgumentException{
         Matcher matcher = PATTERN_GEOMETRY.matcher(geometry);
         if( ! matcher.matches() ){
@@ -143,9 +137,9 @@ public class OptionInfo{
         int gpos = 1;
         String width  = matcher.group(gpos++);
         String height = matcher.group(gpos++);
-        String xSign  = matcher.group(gpos++);
+        String xMinus = matcher.group(gpos++);
         String xPos   = matcher.group(gpos++);
-        String ySign  = matcher.group(gpos++);
+        String yMinus = matcher.group(gpos++);
         String yPos   = matcher.group(gpos++);
 
         info.frameWidth  = Integer.parseInt(width);
@@ -153,14 +147,14 @@ public class OptionInfo{
 
         if(xPos != null){
             info.frameXpos = Integer.parseInt(xPos);
-            if(isMinus(xSign)){
+            if(xMinus != null){
                 info.frameXpos = -info.frameXpos;
             }
         }
 
         if(yPos != null){
             info.frameYpos = Integer.parseInt(yPos);
-            if(isMinus(ySign)){
+            if(yMinus != null){
                 info.frameYpos = -info.frameYpos;
             }
         }
@@ -169,18 +163,17 @@ public class OptionInfo{
     }
 
     /**
-     * オプション引数を解析する。
-     * @param result オプション情報
+     * 引数付きオプションを解析する。
+     * @param info オプション情報
      * @param optTxt オプション文字列
      * @param option オプション種別
-     * @param iterator オプション並び
-     * @return 引数と同じオプション情報
-     * @throws IllegalArgumentException 構文エラー
+     * @param iterator コマンドライン引数並び
+     * @throws IllegalArgumentException オプションの引数がない
      */
-    private static OptionInfo parseOptionArg(OptionInfo result,
-                                             String optTxt,
-                                             CmdOption option,
-                                             Iterator<String> iterator )
+    private static void parseOptionArg(OptionInfo info,
+                                         String optTxt,
+                                         CmdOption option,
+                                         Iterator<String> iterator )
             throws IllegalArgumentException {
         String nextArg;
         if(iterator.hasNext()){
@@ -191,17 +184,17 @@ public class OptionInfo{
         }
 
         if(option == CmdOption.OPT_GEOMETRY){
-            parseGeometry(result, optTxt, nextArg);
+            parseGeometry(info, optTxt, nextArg);
         }else if(option.isBooleanOption()){
-            parseBooleanSwitch(result, option, optTxt, nextArg);
+            parseBooleanSwitch(info, option, optTxt, nextArg);
         }else if(   option == CmdOption.OPT_INITFONT
                  || option == CmdOption.OPT_CONFDIR ){
-            result.stringOptionMap.put(option, nextArg);
+            info.stringOptionMap.put(option, nextArg);
         }else{
             assert false;
         }
 
-        return result;
+        return;
     }
 
     /**
index 8840f4a..53fc329 100644 (file)
@@ -18,7 +18,8 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.SortedSet;
 import java.util.TreeSet;
-import jp.sfjp.jindolf.log.LogWrapper;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import jp.sfjp.jindolf.net.HtmlSequence;
 import jp.sfjp.jindolf.net.ServerAccess;
 import jp.sourceforge.jindolf.corelib.LandDef;
@@ -39,7 +40,7 @@ public class Land {
     // 古国ID
     private static final String ID_VANILLAWOLF = "wolf";
 
-    private static final LogWrapper LOGGER = new LogWrapper();
+    private static final Logger LOGGER = Logger.getAnonymousLogger();
 
 
     private final LandDef landDef;
@@ -122,7 +123,7 @@ public class Land {
         try{
             uri = new URI(pureHREF);
         }catch(URISyntaxException e){
-            LOGGER.warn(
+            LOGGER.warning(
                      "不正なURI["
                     + hrefValue
                     + "]を検出しました");
@@ -209,7 +210,7 @@ public class Land {
         try{
             image = server.downloadImage(imageURL);
         }catch(IOException e){
-            LOGGER.warn(
+            LOGGER.log(Level.WARNING,
                     "イメージ[" + imageURL + "]"
                     + "のダウンロードに失敗しました",
                     e );
@@ -239,14 +240,15 @@ public class Land {
     }
 
     /**
-     * 村リストを更新する。
-     * 元情報は国のトップページと村一覧ページ。
+     * 村一覧情報をダウンロードする。
+     * リスト元情報は国のトップページと村一覧ページ。
      * 古国の場合は村一覧にアクセスせずトップページのみ。
-     * å\8f¤å\9b½ä»¥å¤\96ã\81«村建てをやめた国はトップページにアクセスしない。
+     * å\8f¤å\9b½ä»¥å¤\96ã\81§村建てをやめた国はトップページにアクセスしない。
      * 村リストはVillageの実装に従いソートされる。重複する村は排除。
      * @throws java.io.IOException ネットワーク入出力の異常
+     * @return ソートされた村一覧
      */
-    public void updateVillageList() throws IOException{
+    public SortedSet<Village> downloadVillageList() throws IOException {
         LandDef thisLand = getLandDef();
         LandState state = thisLand.getLandState();
         boolean isVanillaWolf = thisLand.getLandId().equals(ID_VANILLAWOLF);
@@ -254,7 +256,7 @@ public class Land {
         ServerAccess server = getServerAccess();
 
         // たまに同じ村が複数回出現するので注意!
-        SortedSet<Village> vset = new TreeSet<Village>();
+        SortedSet<Village> result = new TreeSet<Village>();
 
         // トップページ
         if(state.equals(LandState.ACTIVE) || isVanillaWolf){
@@ -263,11 +265,11 @@ public class Land {
             try{
                 this.parser.parseAutomatic(content);
             }catch(HtmlParseException e){
-                LOGGER.warn("トップページを認識できない", e);
+                LOGGER.log(Level.WARNING, "トップページを認識できない", e);
             }
             List<Village> list = this.handler.getVillageList();
             if(list != null){
-                vset.addAll(list);
+                result.addAll(list);
             }
         }
 
@@ -278,21 +280,28 @@ public class Land {
             try{
                 this.parser.parseAutomatic(content);
             }catch(HtmlParseException e){
-                LOGGER.warn("村一覧ページを認識できない", e);
+                LOGGER.log(Level.WARNING, "村一覧ページを認識できない", e);
             }
             List<Village> list = this.handler.getVillageList();
             if(list != null){
-                vset.addAll(list);
+                result.addAll(list);
             }
         }
 
-        // TODO 村リスト更新のイベントリスナがあると便利か?
-        this.villageList.clear();
-        this.villageList.addAll(vset);
-
         this.parser.reset();
         this.handler.reset();
 
+        return result;
+    }
+
+    /**
+     * 村リストを更新する。
+     * @param vset ソート済みの村一覧
+     */
+    public void updateVillageList(SortedSet<Village> vset){
+        // TODO 村リスト更新のイベントリスナがあると便利か?
+        this.villageList.clear();
+        this.villageList.addAll(vset);
         return;
     }
 
@@ -401,7 +410,7 @@ public class Land {
             String villageID = getVillageIDFromHREF(href);
             if(   villageID == null
                || villageID.length() <= 0 ){
-                LOGGER.warn(
+                LOGGER.warning(
                         "認識できないURL[" + href + "]に遭遇しました。");
                  return;
             }
index 39a54d2..d8263f6 100644 (file)
@@ -14,6 +14,8 @@ import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import javax.swing.event.EventListenerList;
 import javax.swing.event.TreeModelEvent;
 import javax.swing.event.TreeModelListener;
@@ -22,7 +24,6 @@ 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;
 
@@ -36,7 +37,7 @@ 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 static final Logger LOGGER = Logger.getAnonymousLogger();
 
 
     private final List<Land> landList = new LinkedList<Land>();
@@ -60,13 +61,10 @@ public class LandsModel implements TreeModel{ // ComboBoxModelも付けるか?
     }
 
     /**
-     * 指定した国の村一覧を読み込む
+     * 指定した国の村一覧を更新しイベントを投げる
      * @param land 国
-     * @throws java.io.IOException ネットワーク入出力の異常
      */
-    public void loadVillageList(Land land) throws IOException{
-        land.updateVillageList();
-
+    public void updateVillageList(Land land){
         List<VillageSection> sectionList =
                 VillageSection.getSectionList(land, SECTION_INTERVAL);
         this.sectionMap.put(land, sectionList);
@@ -102,16 +100,16 @@ public class LandsModel implements TreeModel{ // ComboBoxModelも付けるか?
             DocumentBuilder builder = XmlUtils.createDocumentBuilder();
             landDefList = LandDef.buildLandDefList(builder);
         }catch(IOException e){
-            LOGGER.fatal("failed to load land list", e);
+            LOGGER.log(Level.SEVERE, "failed to load land list", e);
             return;
         }catch(SAXException e){
-            LOGGER.fatal("failed to load land list", e);
+            LOGGER.log(Level.SEVERE, "failed to load land list", e);
             return;
         }catch(URISyntaxException e){
-            LOGGER.fatal("failed to load land list", e);
+            LOGGER.log(Level.SEVERE, "failed to load land list", e);
             return;
         }catch(ParserConfigurationException e){
-            LOGGER.fatal("failed to load land list", e);
+            LOGGER.log(Level.SEVERE, "failed to load land list", e);
             return;
         }
 
index a120970..9886682 100644 (file)
@@ -15,7 +15,8 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import jp.sfjp.jindolf.log.LogWrapper;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import jp.sfjp.jindolf.net.HtmlSequence;
 import jp.sfjp.jindolf.net.ServerAccess;
 import jp.sfjp.jindolf.util.StringUtils;
@@ -50,7 +51,7 @@ public class Period{
     private static final PeriodHandler HANDLER =
             new PeriodHandler();
 
-    private static final LogWrapper LOGGER = new LogWrapper();
+    private static final Logger LOGGER = Logger.getAnonymousLogger();
 
     static{
         PARSER.setBasicHandler   (HANDLER);
@@ -176,7 +177,7 @@ public class Period{
         try{
             PARSER.parseAutomatic(content);
         }catch(HtmlParseException e){
-            LOGGER.warn("発言抽出に失敗", e);
+            LOGGER.log(Level.WARNING, "発言抽出に失敗", e);
         }
 
         if(wasHot && ! period.isHot() ){
index 1345dc7..4fbeeb4 100644 (file)
@@ -16,7 +16,8 @@ import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
-import jp.sfjp.jindolf.log.LogWrapper;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import jp.sfjp.jindolf.net.HtmlSequence;
 import jp.sfjp.jindolf.net.ServerAccess;
 import jp.sfjp.jindolf.util.GUIUtils;
@@ -77,7 +78,7 @@ public class Village implements Comparable<Village> {
     private static final VillageHeadHandler HANDLER =
             new VillageHeadHandler();
 
-    private static final LogWrapper LOGGER = new LogWrapper();
+    private static final Logger LOGGER = Logger.getAnonymousLogger();
 
     static{
         PARSER.setBasicHandler   (HANDLER);
@@ -169,7 +170,7 @@ public class Village implements Comparable<Village> {
         try{
             PARSER.parseAutomatic(content);
         }catch(HtmlParseException e){
-            LOGGER.warn("村の状態が不明", e);
+            LOGGER.log(Level.WARNING, "村の状態が不明", e);
         }
 
         return;
@@ -800,7 +801,7 @@ public class Village implements Comparable<Village> {
             if(villageState == VillageState.UNKNOWN){
                 this.village.setState(villageState);
                 this.village.periodList.clear();
-                LOGGER.warn("村の状況を読み取れません");
+                LOGGER.warning("村の状況を読み取れません");
                 return;
             }
 
diff --git a/src/main/java/jp/sfjp/jindolf/data/package-info.java b/src/main/java/jp/sfjp/jindolf/data/package-info.java
new file mode 100644 (file)
index 0000000..8b52505
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * パッケージ情報
+ *
+ * License : The MIT License
+ * Copyright(c) 2012 olyutorskii
+ */
+
+/**
+ * 各種データモデルクラス。
+ */
+
+package jp.sfjp.jindolf.data;
+
+/* EOF */
index 606190e..c28b006 100644 (file)
@@ -8,8 +8,6 @@
 package jp.sfjp.jindolf.dxchg;
 
 import java.awt.Component;
-import java.beans.PropertyChangeEvent;
-import java.beans.PropertyChangeListener;
 import javax.swing.Action;
 import javax.swing.JPopupMenu;
 import javax.swing.text.Document;
@@ -20,10 +18,7 @@ import javax.swing.text.JTextComponent;
  * 各種クリップボード操作(カット、コピー、ペースト、etc.)を備える。
  */
 @SuppressWarnings("serial")
-public class TextPopup extends JPopupMenu implements PropertyChangeListener{
-
-    /** プロパティ変更イベントキー。 */
-    private static final String PROPERTY_UI = "UI";
+public class TextPopup extends JPopupMenu {
 
     private final Action cutAction    = ClipboardAction.cutAction();
     private final Action copyAction   = ClipboardAction.copyAction();
@@ -86,27 +81,6 @@ public class TextPopup extends JPopupMenu implements PropertyChangeListener{
 
     /**
      * {@inheritDoc}
-     * ついでにL&F変更監視機構を仕込む。
-     * @param invoker {@inheritDoc}
-     */
-    @Override
-    public void setInvoker(Component invoker){
-        Component old = getInvoker();
-        if(old != null){
-            old.removePropertyChangeListener(this);
-        }
-
-        super.setInvoker(invoker);
-
-        if(invoker != null){
-            invoker.addPropertyChangeListener(PROPERTY_UI, this);
-        }
-
-        return;
-    }
-
-    /**
-     * {@inheritDoc}
      * 文字列選択状況によって一部のポップアップメニューをマスクする。
      * @param invoker {@inheritDoc}
      * @param x {@inheritDoc}
@@ -137,17 +111,5 @@ public class TextPopup extends JPopupMenu implements PropertyChangeListener{
         return;
     }
 
-    /**
-     * {@inheritDoc}
-     * ポップアップ呼び出し元を監視してL&Fを変更する。
-     * @param event {@inheritDoc}
-     */
-    @Override
-    public void propertyChange(PropertyChangeEvent event){
-        String propertyName = event.getPropertyName();
-        if(PROPERTY_UI.equals(propertyName)) updateUI();
-        return;
-    }
-
     // TODO アクセス権チェックによるポップアップメニュー変更
 }
index 9e1e390..dc794fb 100644 (file)
@@ -22,6 +22,7 @@ import java.awt.event.WindowEvent;
 import java.io.IOException;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.util.logging.Logger;
 import javax.swing.BorderFactory;
 import javax.swing.Icon;
 import javax.swing.JButton;
@@ -37,7 +38,6 @@ 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;
 
@@ -53,7 +53,7 @@ public class WebIPCDialog
             VerInfo.getFrameTitle("URLへのアクセス確認");
 
 
-    private static final LogWrapper LOGGER = new LogWrapper();
+    private static final Logger LOGGER = Logger.getAnonymousLogger();
 
 
     private final String warnMessage;
index b78f7c8..d7b8f34 100644 (file)
@@ -22,11 +22,11 @@ import java.util.Properties;
 import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
+import java.util.logging.Logger;
 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;
 
@@ -55,7 +55,7 @@ public final class WolfBBS{
 
     private static final String WOLFBBS_URL = "http://wolfbbs.jp/";
 
-    private static final LogWrapper LOGGER = new LogWrapper();
+    private static final Logger LOGGER = Logger.getAnonymousLogger();
 
     static{
         try{
@@ -90,7 +90,7 @@ public final class WolfBBS{
     private static void loadFaceIconSet() throws FileNotFoundException {
         Properties properties = ResourceManager.getProperties(FACEICONSET);
         if(properties == null){
-            LOGGER.fatal("顔アイコンセットの読み込みに失敗しました");
+            LOGGER.severe("顔アイコンセットの読み込みに失敗しました");
             throw new FileNotFoundException();
         }
 
@@ -110,7 +110,7 @@ public final class WolfBBS{
         if(   codeCheck == null
            || codeCheck.length() != 1
            || codeCheck.charAt(0) != '\u72fc'){  // 「狼」
-            LOGGER.fatal(
+            LOGGER.severe(
                     "顔アイコンセットプロパティファイルの"
                     +"文字コードがおかしいようです。"
                     +"native2ascii は正しく適用しましたか?");
diff --git a/src/main/java/jp/sfjp/jindolf/glyph/Font2Json.java b/src/main/java/jp/sfjp/jindolf/glyph/Font2Json.java
new file mode 100644 (file)
index 0000000..af55df3
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+ * font <-> JSON exchanging
+ *
+ * License : The MIT License
+ * Copyright(c) 2012 olyutorskii
+ */
+
+package jp.sfjp.jindolf.glyph;
+
+import java.awt.Font;
+import java.awt.font.FontRenderContext;
+import jp.sourceforge.jovsonz.JsBoolean;
+import jp.sourceforge.jovsonz.JsNumber;
+import jp.sourceforge.jovsonz.JsObject;
+import jp.sourceforge.jovsonz.JsPair;
+import jp.sourceforge.jovsonz.JsString;
+import jp.sourceforge.jovsonz.JsValue;
+
+/**
+ * フォント情報とJSONとのデータ交換を行う。
+ */
+public final class Font2Json {
+
+    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 Font2Json(){
+        assert false;
+    }
+
+
+    /**
+     * フォント設定をJSON形式にエンコードする。
+     * @param fontInfo フォント設定
+     * @return JSON Object
+     */
+    public static JsObject buildJson(FontInfo fontInfo){
+        Font font             = fontInfo.getFont();
+        FontRenderContext frc = fontInfo.getFontRenderContext();
+
+        JsPair family = new JsPair(HASH_FAMILY,
+                                   fontInfo.getRootFamilyName() );
+        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 aa     = new JsPair(HASH_USEAA, frc.isAntiAliased());
+        JsPair frac   = new JsPair(HASH_FRACTIONAL,
+                                   frc.usesFractionalMetrics() );
+
+        JsObject result = new JsObject();
+        result.putPair(family);
+        result.putPair(size);
+        result.putPair(bold);
+        result.putPair(italic);
+        result.putPair(aa);
+        result.putPair(frac);
+
+        return result;
+    }
+
+    /**
+     * JSONからフォントを復元。
+     * @param obj JSON Object
+     * @return フォント
+     */
+    private static Font decodeJsonFont(JsObject obj){
+        JsValue value;
+
+        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){
+                font = decoded;
+            }
+        }
+        if(font == null){
+            font = FontInfo.DEFAULT_FONTINFO.getFont();
+        }
+
+        boolean isBold   = false;
+        boolean isItalic = false;
+
+        value = obj.getValue(HASH_ISBOLD);
+        if(value instanceof JsBoolean){
+            JsBoolean bool = (JsBoolean) value;
+            isBold = bool.booleanValue();
+        }
+
+        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;
+
+        int size = FontInfo.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 ){
+        JsBoolean jsAntiAlias = null;
+        JsBoolean jsFractional = null;
+
+        boolean isAntiAlias = false;
+        boolean useFractional = false;
+
+        JsValue value;
+        value = obj.getValue(HASH_USEAA);
+        if(value instanceof JsBoolean){
+            jsAntiAlias = (JsBoolean) value;
+            isAntiAlias = jsAntiAlias.booleanValue();
+        }
+
+        value = obj.getValue(HASH_FRACTIONAL);
+        if(value instanceof JsBoolean){
+            jsFractional = (JsBoolean) value;
+            useFractional = jsFractional.booleanValue();
+        }
+
+        if(jsAntiAlias == null || jsFractional == null){
+            FontRenderContext defFrc = FontInfo.createBestContext(font);
+            if(jsAntiAlias == null){
+                isAntiAlias = defFrc.isAntiAliased();
+            }
+            if(jsFractional == null){
+                useFractional = defFrc.usesFractionalMetrics();
+            }
+        }
+
+        FontRenderContext newFrc =
+                new FontRenderContext(ImtblAffineTx.IDENTITY,
+                                      isAntiAlias, useFractional );
+
+        return 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;
+    }
+
+}
index 76fe2e2..ce078f9 100644 (file)
@@ -19,6 +19,7 @@ import java.awt.event.ActionListener;
 import java.awt.event.ItemEvent;
 import java.awt.event.ItemListener;
 import java.text.MessageFormat;
+import java.util.logging.Logger;
 import javax.swing.BorderFactory;
 import javax.swing.JButton;
 import javax.swing.JCheckBox;
@@ -32,7 +33,6 @@ 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;
 
 /**
@@ -50,7 +50,7 @@ public class FontChooser extends JPanel
     private static final CharSequence PREVIEW_CONTENT;
     private static final int UNIT_INC = 8;
 
-    private static final LogWrapper LOGGER = new LogWrapper();
+    private static final Logger LOGGER = Logger.getAnonymousLogger();
 
     static{
         PREVIEW_CONTENT =
@@ -86,6 +86,7 @@ public class FontChooser extends JPanel
      * @param fontInfo 初期フォント設定
      * @throws NullPointerException 引数がnull
      */
+    @SuppressWarnings("LeakingThisInConstructor")
     public FontChooser(FontInfo fontInfo)
             throws NullPointerException{
         super();
@@ -363,7 +364,7 @@ public class FontChooser extends JPanel
             .setSelected(this.fontInfo.usesFractionalMetrics());
 
         // デコード名
-        this.decodeName.setText(FontUtils.getFontDecodeName(currentFont));
+        this.decodeName.setText(getFontInfo().getFontDecodeName());
         this.decodeName.setCaretPosition(0);
 
         // 寸法
diff --git a/src/main/java/jp/sfjp/jindolf/glyph/FontEnv.java b/src/main/java/jp/sfjp/jindolf/glyph/FontEnv.java
new file mode 100644 (file)
index 0000000..c9f9d7f
--- /dev/null
@@ -0,0 +1,275 @@
+/*
+ * font environment
+ *
+ * License : The MIT License
+ * Copyright(c) 2012 olyutorskii
+ */
+
+package jp.sfjp.jindolf.glyph;
+
+import java.awt.Font;
+import java.awt.GraphicsEnvironment;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+/**
+ * フォント環境に関する情報あれこれをバックグラウンドで収集する。
+ * <p>
+ * <ul>
+ * <li>与えられた選択肢から利用可能なフォントを一つ選ぶこと
+ * <li>任意の文字列を表示可能な全フォントを列挙すること
+ * </ul>
+ * この二つをバックグラウンドで非同期に収集する。
+ * <p>各種フォント環境収集メソッドの遅さをカバーするのが実装目的。
+ */
+public class FontEnv {
+
+    /**
+     * デフォルトのフォント環境。
+     */
+    public static final FontEnv DEFAULT;
+
+    /** {@link java.awt.Font#DIALOG} 代替品。 */
+    private static final String FAMILY_DIALOG = "Dialog";
+
+    /** フォントファミリ選択肢。 */
+    private static final String[] INIT_FAMILY_NAMES = {
+        "Hiragino Kaku Gothic Pro",  // for MacOS X
+        "Hiragino Kaku Gothic Std",
+        "Osaka",
+        "MS PGothic",                // for WinXP
+        "MS Gothic",
+        "IPAMonaPGothic",
+        // TODO X11用のおすすめは?
+    };
+
+    /** JIS X0208:1990 表示確認用文字列。 */
+    private static final String JPCHECK_CODE = "あ凜熙峠ゑアアヴヰ┼ЖΩ9A";
+
+    /** {@link java.util.Locale#ROOT} 代替品。 */
+    private static final Locale LOCALE_ROOT = new Locale("", "", "");
+
+    private static final int POOL_SZ = 2;
+    private static final int STRIDE = 15;
+
+    static{
+        DEFAULT = new FontEnv(JPCHECK_CODE, INIT_FAMILY_NAMES);
+    }
+
+
+    private final String proveChars;
+    private final List<String> fontFamilyList;
+
+    private final Future<List<String>> listLoadFuture;
+    private final Future<String>       fontSelectFuture;
+
+
+    /**
+     * コンストラクタ。
+     * 完了と同時に裏でフォント情報収集タスクが走る。
+     * @param proveChars 表示判定用文字列
+     * @param fontFamilyList フォント候補
+     * @throws NullPointerException 引数がnull
+     */
+    public FontEnv(String proveChars, List<String> fontFamilyList)
+            throws NullPointerException {
+        super();
+
+        if(proveChars == null || fontFamilyList == null){
+            throw new NullPointerException();
+        }
+
+        this.proveChars = proveChars;
+        this.fontFamilyList = fontFamilyList;
+
+        ExecutorService service = Executors.newFixedThreadPool(POOL_SZ);
+
+        Callable<List<String>> loadTask = new FontListLoader();
+        this.listLoadFuture = service.submit(loadTask);
+
+        Callable<String> selectTask = new FontSelector();
+        this.fontSelectFuture = service.submit(selectTask);
+
+        service.shutdown();
+
+        return;
+    }
+
+    /**
+     * コンストラクタ。
+     * 完了と同時に裏でフォント情報収集タスクが走る。
+     * @param proveChars 表示判定用文字列
+     * @param fontFamilyList フォント候補
+     * @throws NullPointerException 引数がnull
+     */
+    public FontEnv(String proveChars, String ... fontFamilyList)
+            throws NullPointerException {
+        this(proveChars, Arrays.asList(fontFamilyList));
+        return;
+    }
+
+
+    /**
+     * 自発的なスケジューリングを促す。
+     */
+    @SuppressWarnings("CallToThreadYield")
+    protected static void yield(){
+        Thread.yield();
+        return;
+    }
+
+    /**
+     * 指定文字列が表示可能なフォントファミリ集合を生成する。
+     * <p>結構実行時間がかかるかも(数千ms)。乱用禁物。
+     * @param checkChars テスト対象の文字が含まれた文字列
+     * @return フォント集合
+     */
+    protected static Collection<String> createFontSet(String checkChars){
+        GraphicsEnvironment ge =
+                GraphicsEnvironment.getLocalGraphicsEnvironment();
+        Font[] allFonts = ge.getAllFonts();
+
+        yield();
+
+        Collection<String> result = new HashSet<String>();
+        int ct = 0;
+        for(Font font : allFonts){
+            if(++ct % STRIDE == 0) yield();
+
+            String familyName = font.getFamily();
+            if(result.contains(familyName)) continue;
+            if(font.canDisplayUpTo(checkChars) >= 0) continue;
+
+            result.add(familyName);
+        }
+
+        return result;
+    }
+
+    /**
+     * システムに存在する有効なファミリ名か判定する。
+     * @param familyName フォントファミリ名。
+     * @return 存在する有効なファミリ名ならtrue
+     */
+    protected static boolean isValidFamilyName(String familyName){
+        int style = 0x00 | Font.PLAIN;
+        int size = 1;
+        Font dummyFont = new Font(familyName, style, size);
+
+        String dummyFamilyName = dummyFont.getFamily(LOCALE_ROOT);
+        if(dummyFamilyName.equals(familyName)) return true;
+
+        String dummyLocalFamilyName = dummyFont.getFamily();
+        if(dummyLocalFamilyName.equals(familyName)) return true;
+
+        return false;
+    }
+
+    /**
+     * 複数の候補から利用可能なフォントを一つ選び、生成する。
+     * 候補から適当なファミリが見つからなかったら"Dialog"が選択される。
+     * @param fontList フォントファミリ名候補
+     * @return フォント
+     */
+    protected static String availableFontFamily(Iterable<String> fontList){
+        String defaultFamilyName = FAMILY_DIALOG;
+        for(String familyName : fontList){
+            if(isValidFamilyName(familyName)){
+                defaultFamilyName = familyName;
+                break;
+            }
+        }
+
+        return defaultFamilyName;
+    }
+
+
+    /**
+     * フォントファミリー名のリストを返す。
+     * @return フォントファミリー名のリスト
+     * @throws IllegalStateException 収集タスクに異常が発生
+     */
+    public List<String> getFontFamilyList() throws IllegalStateException {
+        List<String> result;
+
+        try{
+            result = this.listLoadFuture.get();
+        }catch(ExecutionException e){
+            throw new IllegalStateException(e);
+        }catch(InterruptedException e){
+            throw new IllegalStateException(e);
+        }
+
+        return result;
+    }
+
+    /**
+     * 利用可能なフォントファミリ名を返す。
+     * @return フォントファミリー名
+     * @throws IllegalStateException 収集タスクに異常が発生
+     */
+    public String selectFontFamily() throws IllegalStateException {
+        String result;
+
+        try{
+            result = this.fontSelectFuture.get();
+        }catch(ExecutionException e){
+            throw new IllegalStateException(e);
+        }catch(InterruptedException e){
+            throw new IllegalStateException(e);
+        }
+
+        return result;
+    }
+
+
+    /**
+     * フォントリスト収集タスク。
+     */
+    protected final class FontListLoader implements Callable<List<String>> {
+        /**
+         * {@inheritDoc}
+         * @return {@inheritDoc}
+         */
+        @Override
+        public List<String> call(){
+            Collection<String> fontSet =
+                    createFontSet(FontEnv.this.proveChars);
+            yield();
+
+            List<String> result = new ArrayList<String>(fontSet);
+            Collections.sort(result);
+            yield();
+
+            result = Collections.unmodifiableList(result);
+
+            return result;
+        }
+    }
+
+    /**
+     * フォント選択タスク。
+     */
+    protected final class FontSelector implements Callable<String> {
+        /**
+         * {@inheritDoc}
+         * @return {@inheritDoc}
+         */
+        @Override
+        public String call(){
+            String result = availableFontFamily(FontEnv.this.fontFamilyList);
+            return result;
+        }
+    }
+
+}
index 5d2dfc6..9f4d71c 100644 (file)
@@ -14,31 +14,41 @@ 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;
-import jp.sourceforge.jovsonz.JsObject;
-import jp.sourceforge.jovsonz.JsPair;
-import jp.sourceforge.jovsonz.JsString;
-import jp.sourceforge.jovsonz.JsValue;
+import java.util.Locale;
 
 /**
  * フォント描画に関する各種設定。
  */
 public class FontInfo{
 
+    /** デフォルトのフォント環境。 */
+    public static final FontEnv DEFAULT_FONTENV;
     /** デフォルトのフォント設定。 */
-    public static final FontInfo DEFAULT_FONTINFO = new FontInfo();
+    public static final FontInfo DEFAULT_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_FRACTIONAL = "useFractional";
+    /** デフォルトのポイントサイズ。 */
+    public static final int DEF_SIZE = 16;
+    /** デフォルトのフォントスタイル。 */
+    public static final int DEF_STYLE = 0x00 | Font.PLAIN;
 
+    /** {@link java.util.Locale#ROOT}代替品。 */
+    private static final Locale LOCALE_ROOT = new Locale("", "", "");
+    /** MSリコー系日本語ベクトルフォント下限ポイントサイズ。 */
+    private static final int MS_VEC_LIMIT = 24;
+    /** 二重引用符。 */
+    private static final char DQ = '"';
 
-    private final Font font;
-    private final FontRenderContext context;
+
+    static{
+        DEFAULT_FONTENV = FontEnv.DEFAULT;
+        DEFAULT_FONTINFO = new FontInfo();
+    }
+
+
+    // いずれのフィールドもnull値はデフォルト値の遅延評価フラグ
+    private String familyName;
+    private Font font;
+    private FontRenderContext context;
 
 
     /**
@@ -46,19 +56,7 @@ public class FontInfo{
      * デフォルトフォントとそれに適した描画属性が指定される。
      */
     public FontInfo(){
-        this(FontUtils.createDefaultSpeechFont());
-        return;
-    }
-
-    /**
-     * コンストラクタ。
-     * 描画設定はフォント属性に応じて自動的に調整される。
-     * @param font フォント
-     * @throws NullPointerException 引数がnull
-     */
-    public FontInfo(Font font)
-            throws NullPointerException{
-        this(font, createBestContext(font));
+        this((Font)null, (FontRenderContext)null);
         return;
     }
 
@@ -66,12 +64,10 @@ public class FontInfo{
      * コンストラクタ。
      * @param font フォント
      * @param context 描画設定
-     * @throws NullPointerException 引数がnull
      */
-    public FontInfo(Font font, FontRenderContext context)
-            throws NullPointerException{
+    public FontInfo(Font font, FontRenderContext context){
         super();
-        if(font == null || context == null) throw new NullPointerException();
+        this.familyName = null;
         this.font = font;
         this.context = context;
         return;
@@ -79,156 +75,80 @@ public class FontInfo{
 
 
     /**
-     * フォントに応じた最適な描画設定を生成する。
-     * <p>ビットマップフォントと推測されるときは
-     * アンチエイリアスやサブピクセル補完を無効にする。
+     * マイクロソフト&リコー(リョービイマジクス)系
+     * 日本語ベクトルフォントか否か、ファミリ名で見当をつける。
+     * <p>日本語Windows同梱のMSゴシックやMS明朝などが対象。
+     * <p>メイリオは対象外。
      * @param font フォント
-     * @return 描画設定
-     * @see FontUtils#guessBitmapFont(Font)
+     * @return 見当が付けばtrue
      */
-    public static FontRenderContext createBestContext(Font font){
-        boolean isAntiAliased         = true;
-        boolean usesFractionalMetrics = true;
-        if(FontUtils.guessBitmapFont(font)){
-            isAntiAliased         = false;
-            usesFractionalMetrics = false;
+    protected static boolean isMsRicohJpFont(Font font){
+        String rootFamilyName = font.getFamily(LOCALE_ROOT);
+        if(rootFamilyName.startsWith("MS")){
+            if(rootFamilyName.contains("Gothic")) return true;
+            if(rootFamilyName.contains("Mincho")) return true;
         }
-
-        AffineTransform identity = ImtblAffineTx.IDENTITY;
-        FontRenderContext result =
-                new FontRenderContext(identity,
-                                      isAntiAliased,
-                                      usesFractionalMetrics);
-
-        return result;
+        return false;
     }
 
     /**
-     * フォント設定をJSON形式にエンコードする。
-     * @param fontInfo フォント設定
-     * @return JSON Object
+     * ビットマップフォントか否か見当をつける。
+     * <p>判定基準はかなりアバウト。
+     * 実用上、小さめのMSPゴシックを補足できればそれでいい。
+     * <p>ビットマップフォントには
+     * アンチエイリアスやサブピクセルを使わないほうが
+     * 見栄えがいいような気がする。
+     * @param font 判定対象フォント
+     * @return ビットマップフォントらしかったらtrue
      */
-    public static JsObject buildJson(FontInfo fontInfo){
-        Font font             = fontInfo.getFont();
-        FontRenderContext frc = fontInfo.getFontRenderContext();
-
-        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 aa     = new JsPair(HASH_USEAA, frc.isAntiAliased());
-        JsPair frac   = new JsPair(HASH_FRACTIONAL,
-                                   frc.usesFractionalMetrics() );
-
-        JsObject result = new JsObject();
-        result.putPair(family);
-        result.putPair(size);
-        result.putPair(bold);
-        result.putPair(italic);
-        result.putPair(aa);
-        result.putPair(frac);
-
-        return result;
+    protected static boolean isBitmapFont(Font font){
+        if(font.getSize() >= MS_VEC_LIMIT) return false;
+        if(isMsRicohJpFont(font)) return true;
+        return false;
     }
 
     /**
-     * JSONからフォントを復元。
-     * @param obj JSON Object
-     * @return フォント
+     * フォントに応じた最適な描画設定を生成する。
+     * <p>ビットマップフォントと推測されるときは
+     * アンチエイリアスやサブピクセル補完を無効にする。
+     * @param font フォント
+     * @return 描画設定
      */
-    private static Font decodeJsonFont(JsObject obj){
-        JsValue value;
-
-        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){
-                font = decoded;
-            }
-        }
-        if(font == null){
-            font = FontUtils.createDefaultSpeechFont();
-        }
-
-        boolean isBold   = false;
-        boolean isItalic = false;
-
-        value = obj.getValue(HASH_ISBOLD);
-        if(value instanceof JsBoolean){
-            JsBoolean bool = (JsBoolean) value;
-            isBold = bool.booleanValue();
-        }
+    protected static FontRenderContext createBestContext(Font font){
+        boolean isAntiAliased;
+        boolean usesFractionalMetrics;
 
-        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;
-
-        int size = FontUtils.DEF_SIZE;
-        value = obj.getValue(HASH_SIZE);
-        if(value instanceof JsNumber){
-            JsNumber number = (JsNumber) value;
-            size = number.intValue();
+        if(isBitmapFont(font)){
+            isAntiAliased         = false;
+            usesFractionalMetrics = false;
+        }else{
+            isAntiAliased         = true;
+            usesFractionalMetrics = true;
         }
 
-        Font derivedFont = font.deriveFont(style, (float)size);
+        AffineTransform identity = ImtblAffineTx.IDENTITY;
+        FontRenderContext result;
+        result = new FontRenderContext(identity,
+                                       isAntiAliased,
+                                       usesFractionalMetrics );
 
-        return derivedFont;
+        return result;
     }
 
     /**
-     * JSONからフォント描画設定を復元。
-     * @param obj JSON Object
-     * @param font デフォルトフォント
-     * @return フォント描画設定
+     * ファミリ名を返す。
+     * @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();
-        }
-
-        value = obj.getValue(HASH_FRACTIONAL);
-        if(value instanceof JsBoolean){
-            JsBoolean bool = (JsBoolean) value;
-            useFractional = bool.booleanValue();
+    private String getFamilyName(){
+        if(this.familyName == null){
+            if(this.font == null){
+                this.familyName = DEFAULT_FONTENV.selectFontFamily();
+            }else{
+                // 再帰に注意
+                this.familyName = getRootFamilyName();
+            }
         }
-
-        FontRenderContext newFrc =
-                new FontRenderContext(ImtblAffineTx.IDENTITY,
-                                      isAntiAlias, useFractional );
-
-        return 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;
+        return this.familyName;
     }
 
     /**
@@ -236,6 +156,10 @@ public class FontInfo{
      * @return フォント
      */
     public Font getFont(){
+        if(this.font == null){
+            String name = getFamilyName();
+            this.font = new Font(name, DEF_STYLE, DEF_SIZE);
+        }
         return this.font;
     }
 
@@ -244,6 +168,10 @@ public class FontInfo{
      * @return 描画属性
      */
     public FontRenderContext getFontRenderContext(){
+        if(this.context == null){
+            Font thisFont = getFont();
+            this.context = createBestContext(thisFont);
+        }
         return this.context;
     }
 
@@ -252,7 +180,8 @@ public class FontInfo{
      * @return アンチエイリアス機能を使うならtrue
      */
     public boolean isAntiAliased(){
-        boolean result = this.context.isAntiAliased();
+        FontRenderContext frc = getFontRenderContext();
+        boolean result = frc.isAntiAliased();
         return result;
     }
 
@@ -261,7 +190,8 @@ public class FontInfo{
      * @return サブピクセル精度を使うならtrue
      */
     public boolean usesFractionalMetrics(){
-        boolean result = this.context.usesFractionalMetrics();
+        FontRenderContext frc = getFontRenderContext();
+        boolean result = frc.usesFractionalMetrics();
         return result;
     }
 
@@ -271,7 +201,9 @@ public class FontInfo{
      * @see java.awt.Font#getMaxCharBounds(FontRenderContext)
      */
     public Rectangle getMaxCharBounds(){
-        Rectangle2D r2d = this.font.getMaxCharBounds(this.context);
+        Font thisFont = getFont();
+        FontRenderContext frc = getFontRenderContext();
+        Rectangle2D r2d = thisFont.getMaxCharBounds(frc);
         Rectangle rect = r2d.getBounds();
         return rect;
     }
@@ -282,7 +214,8 @@ public class FontInfo{
      * @return 新設定
      */
     public FontInfo deriveFont(Font newFont){
-        return new FontInfo(newFont, this.context);
+        FontInfo result = new FontInfo(newFont, this.context);
+        return result;
     }
 
     /**
@@ -291,7 +224,8 @@ public class FontInfo{
      * @return 新設定
      */
     public FontInfo deriveRenderContext(FontRenderContext newContext){
-        return new FontInfo(this.font, newContext);
+        FontInfo result = new FontInfo(this.font, newContext);
+        return result;
     }
 
     /**
@@ -301,8 +235,13 @@ public class FontInfo{
      * @return 新設定
      */
     public FontInfo deriveRenderContext(boolean isAntiAliases,
-                                        boolean useFractional){
-        AffineTransform tx = this.context.getTransform();
+                                           boolean useFractional ){
+        AffineTransform tx;
+        if(this.context == null){
+            tx = ImtblAffineTx.IDENTITY;
+        }else{
+            tx = this.context.getTransform();
+        }
         FontRenderContext newContext =
                 new FontRenderContext(tx, isAntiAliases, useFractional);
         return deriveRenderContext(newContext);
@@ -314,12 +253,56 @@ public class FontInfo{
      * @return グリフ集合
      */
     public GlyphVector createGlyphVector(CharacterIterator iterator){
-        GlyphVector glyph =
-                this.font.createGlyphVector(this.context, iterator);
+        Font thisFont = getFont();
+        FontRenderContext frc = getFontRenderContext();
+        GlyphVector glyph = thisFont.createGlyphVector(frc, iterator);
         return glyph;
     }
 
     /**
+     * ロケール中立なフォントファミリ名を返す。
+     * JRE1.5対策
+     * @return ファミリ名
+     * @see Font#getFamily(Locale)
+     */
+    public String getRootFamilyName(){
+        Font thisFont = getFont();
+        String result = thisFont.getFamily(LOCALE_ROOT);
+        return result;
+    }
+
+    /**
+     * Font#decode()用の名前を返す。
+     * 空白が含まれる場合は二重引用符で囲まれる。
+     * @return {@link java.awt.Font#decode(String)}用の名前
+     * @see java.awt.Font#decode(String)
+     */
+    public String getFontDecodeName(){
+        StringBuilder result = new StringBuilder();
+
+        String name = getRootFamilyName();
+
+        Font thisFont = getFont();
+        StringBuilder style = new StringBuilder();
+        if(thisFont.isBold())   style.append("BOLD");
+        if(thisFont.isItalic()) style.append("ITALIC");
+        if(style.length() <= 0) style.append("PLAIN");
+
+        int fontSize = thisFont.getSize();
+
+        result.append(name)
+              .append('-').append(style)
+              .append('-').append(fontSize);
+
+        if(   result.indexOf("\u0020") >= 0
+           || result.indexOf("\u3000") >= 0 ){
+            result.insert(0, DQ).append(DQ);
+        }
+
+        return result.toString();
+    }
+
+    /**
      * {@inheritDoc}
      * @param obj {@inheritDoc}
      * @return {@inheritDoc}
@@ -329,8 +312,17 @@ public class FontInfo{
         if( ! (obj instanceof FontInfo) ) return false;
         FontInfo target = (FontInfo) obj;
 
-        if( ! (this.font   .equals(target.font))    ) return false;
-        if( ! (this.context.equals(target.context)) ) return false;
+        Font thisFont = getFont();
+        Font targetFont = target.getFont();
+        if( ! (thisFont.equals(targetFont)) ){
+            return false;
+        }
+
+        FontRenderContext thisContext = getFontRenderContext();
+        FontRenderContext targetContext = target.getFontRenderContext();
+        if( ! (thisContext.equals(targetContext)) ){
+            return false;
+        }
 
         return true;
     }
@@ -341,7 +333,9 @@ public class FontInfo{
      */
     @Override
     public int hashCode(){
-        return this.font.hashCode() ^ this.context.hashCode();
+        int hashFont = getFont().hashCode();
+        int hashContext = getFontRenderContext().hashCode();
+        return hashFont ^ hashContext;
     }
 
 }
index f7c27f3..9f03099 100644 (file)
@@ -16,7 +16,7 @@ import javax.swing.AbstractListModel;
  * フォントファミリ名一覧表示用リストのデータモデル。
  * <p>環境によってはフォントリストを完成させるのに
  * 数千msかかる場合があるので、その対策として非同期に一覧を読み込む。
- * <p>実際のリスト作成は裏で走るタスクにより行われ、
+ * <p>実際のリスト作成はEDTにより行われ、
  * リスト完成の暁にはEDTによりリスナに通知される。
  * 一般的なリストモデルと同様、
  * 基本的にスレッド間競合の問題はEDTで解決すること。
@@ -24,65 +24,26 @@ import javax.swing.AbstractListModel;
 @SuppressWarnings("serial")
 public class FontListModel extends AbstractListModel {
 
-    private final List<String> familyList = new LinkedList<String>();
+    private static final FontEnv DEFAULT_FONTENV = FontEnv.DEFAULT;
 
-    private volatile boolean hasDone = false;
+    private final List<String> familyList = new LinkedList<String>();
 
     /**
      * コンストラクタ。
-     * <p>ã\82³ã\83³ã\82¹ã\83\88ã\83©ã\82¯ã\82¿å®\8cäº\86ã\81¨å\90\8cæ\99\82ã\81«ã\83ªã\82¹ã\83\88ç\94\9fæ\88\90ã\82¿ã\82¹ã\82¯ã\81\8cè£\8fで走り始める。
+     * <p>ã\83ªã\82¹ã\83\88å\9f\8bã\82\81ã\82¿ã\82¹ã\82¯ã\81\8cEDTで走り始める。
      */
     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);
+                List<String> fontList = DEFAULT_FONTENV.getFontFamilyList();
+                FontListModel model = FontListModel.this;
+                model.familyList.addAll(fontList);
+                int size = model.familyList.size();
                 if(size <= 0) return;
 
                 int begin = 0;
@@ -97,24 +58,6 @@ public class FontListModel extends AbstractListModel {
     }
 
     /**
-     * フォントリスト埋めタスクを起動する。
-     * @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}
diff --git a/src/main/java/jp/sfjp/jindolf/glyph/FontUtils.java b/src/main/java/jp/sfjp/jindolf/glyph/FontUtils.java
deleted file mode 100644 (file)
index 8cca958..0000000
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * font utilities
- *
- * License : The MIT License
- * Copyright(c) 2009 olyutorskii
- */
-
-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.Set;
-
-/**
- * フォントユーティリティ。
- */
-public final class FontUtils{
-
-    /** Font.DIALOG代替品。 */
-    public static final String FAMILY_DIALOG = "Dialog";
-    /** デフォルトのポイントサイズ。 */
-    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
-        "Hiragino Kaku Gothic Std",
-        "Osaka",
-        "MS PGothic",                // for WinXP
-        "MS Gothic",
-        "IPAMonaPGothic",
-        // TODO X11用のおすすめは?
-    };
-
-    /** JIS0208:1990 チェック用。 */
-    private static final String JPCHECK_CODE = "あ凜熙峠ゑアアヴヰ┼ЖΩ9A";
-
-    /** 二重引用符。 */
-    private static final char DQ = '"';
-
-
-    /**
-     * 隠れコンストラクタ。
-     */
-    private FontUtils(){
-        assert false;
-    }
-
-
-    /**
-     * システムに存在する有効なファミリ名か判定する。
-     * @param familyName フォントファミリ名。
-     * @return 存在する有効なファミリ名ならtrue
-     */
-    public static boolean isValidFamilyName(String familyName){
-        int style = 0x00 | Font.PLAIN;
-        int size = 1;
-        Font dummyFont = new Font(familyName, style, size);
-
-        String dummyFamilyName      = getRootFamilyName(dummyFont);
-        String dummyLocalFamilyName = dummyFont.getFamily();
-        if(dummyFamilyName     .equals(familyName)) return true;
-        if(dummyLocalFamilyName.equals(familyName)) return true;
-
-        return false;
-    }
-
-    /**
-     * 発言用のデフォルトフォントを生成する。
-     * 適当なファミリが見つからなかったら"Dialog"が選択される。
-     * @return デフォルトフォント
-     */
-    public static Font createDefaultSpeechFont(){
-        String defaultFamilyName = FAMILY_DIALOG;
-        for(String familyName : INIT_FAMILY_NAMES){
-            if(isValidFamilyName(familyName)){
-                defaultFamilyName = familyName;
-                break;
-            }
-        }
-
-        int style = 0x00 | Font.PLAIN;
-        int size = DEF_SIZE;
-        Font result = new Font(defaultFamilyName, style, size);
-
-        return result;
-    }
-
-    /**
-     * ソートされたフォントファミリ一覧表を生成する。
-     * <p>JISX0208:1990相当が表示できないファミリは弾かれる。
-     * <p>結構実行時間がかかるかも(数千ms)。乱用禁物。
-     * @return フォント一覧
-     */
-    public static List<String> createFontList(){
-        GraphicsEnvironment ge =
-                GraphicsEnvironment.getLocalGraphicsEnvironment();
-        Font[] allFonts = ge.getAllFonts();
-
-        Set<String> checked = new HashSet<String>();
-        for(Font font : allFonts){
-            String familyName = font.getFamily();
-            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 result;
-    }
-
-    /**
-     * ビットマップフォントか否か見当をつける。
-     * ビットマップフォントにはアンチエイリアスやサブピクセルを使わないほうが
-     * 見栄えがいいような気がする。
-     * @param font 判定対象フォント
-     * @return ビットマップフォントらしかったらtrue
-     */
-    public static boolean guessBitmapFont(Font font){
-        String familyName = getRootFamilyName(font);
-        if(   font.getSize() < MS_BMP_LIMIT
-           && familyName.startsWith("MS")
-           && (   familyName.contains("Gothic")
-               || familyName.contains("Mincho")    ) ){
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * 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(style.length() <= 0) style.append("PLAIN");
-
-        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, DQ).append(DQ);
-        }
-
-        return result.toString();
-    }
-
-    /**
-     * ロケール中立なフォントファミリ名を返す。
-     * JRE1.5対策
-     * @param font フォント
-     * @return ファミリ名
-     * @see Font#getFamily(Locale)
-     */
-    public static String getRootFamilyName(Font font){
-        return font.getFamily(LOCALE_ROOT);
-    }
-
-}
index 2672d54..9be7f69 100644 (file)
@@ -7,6 +7,7 @@
 
 package jp.sfjp.jindolf.log;
 
+import java.io.PrintStream;
 import java.util.List;
 import java.util.logging.ConsoleHandler;
 import java.util.logging.Handler;
@@ -22,14 +23,19 @@ public final class LogUtils {
     public static final LoggingPermission PERM_LOGCTL =
             new LoggingPermission("control", null);
 
+    private static final PrintStream STDERR = System.err;
+    private static final String ERRMSG_LOGSECURITY =
+            "セキュリティ設定により、ログ設定を変更できませんでした";
+
+
     /**
      * 隠しコンストラクタ。
      */
     private LogUtils(){
-        super();
-        return;
+        assert false;
     }
 
+
     /**
      * ログ操作のアクセス権があるか否か判定する。
      * @return アクセス権があればtrue
@@ -58,28 +64,36 @@ public final class LogUtils {
     }
 
     /**
+     * ルートロガーを返す。
+     * @return ルートロガー
+     */
+    public static Logger getRootLogger(){
+        Logger rootLogger = Logger.getLogger("");
+        return rootLogger;
+    }
+
+    /**
      * ルートロガーの初期化を行う。
      * ルートロガーの既存ハンドラを全解除し、
-     * {@link PileHandler}ハンドラを登録する。
+     * {@link MomentaryHandler}ハンドラを登録する。
      * @param useConsoleLog trueなら
      * {@link java.util.logging.ConsoleHandler}も追加する。
      */
     public static void initRootLogger(boolean useConsoleLog){
         if( ! hasLoggingPermission() ){
-            System.err.println(
-                      "セキュリティ設定により、"
-                    + "ログ設定を変更できませんでした" );
+            STDERR.println(ERRMSG_LOGSECURITY);
             return;
         }
 
-        Logger rootLogger = Logger.getLogger("");
+        Logger rootLogger = getRootLogger();
 
-        for(Handler handler : rootLogger.getHandlers()){
+        Handler[] oldHandlers = rootLogger.getHandlers();
+        for(Handler handler : oldHandlers){
             rootLogger.removeHandler(handler);
         }
 
-        Handler pileHandler = new PileHandler();
-        rootLogger.addHandler(pileHandler);
+        Handler momentaryHandler = new MomentaryHandler();
+        rootLogger.addHandler(momentaryHandler);
 
         if(useConsoleLog){
             Handler consoleHandler = new ConsoleHandler();
@@ -90,25 +104,27 @@ public final class LogUtils {
     }
 
     /**
-     * ロガーに新ハンドラを追加する。
-     * ã\83­ã\82¬ã\83¼ä¸­ã\81®å\85¨{@link PileHandler}å\9e\8bã\83\8fã\83³ã\83\89ã\83©ã\81«è\93\84ç©\8dã\81\95ã\82\8cã\81¦ã\81\84ã\81\9fã\83­ã\82°ã\81¯ã\80\81
-     * 新ハンドラに一気に転送される。
-     * {@link PileHandler}型ハンドラはロガーから削除される。
+     * ã\83«ã\83¼ã\83\88ã\83­ã\82¬ã\83¼ã\81«æ\96°ã\83\8fã\83³ã\83\89ã\83©ã\82\92追å\8a ã\81\99ã\82\8bã\80\82
+     * ã\83«ã\83¼ã\83\88ã\83­ã\82¬ã\83¼ä¸­ã\81®å\85¨{@link MomentaryHandler}å\9e\8bã\83\8fã\83³ã\83\89ã\83©ã\81«
+     * 蓄積されていたログは、新ハンドラに一気に転送される。
+     * {@link MomentaryHandler}型ハンドラはルートロガーから削除される。
      * ログ操作のパーミッションがない場合、何もしない。
-     * @param logger ロガー
      * @param newHandler 新ハンドラ
      */
-    public static void switchHandler(Logger logger, Handler newHandler){
+    public static void switchHandler(Handler newHandler){
         if( ! hasLoggingPermission() ) return;
 
-        List<PileHandler> pileHandlers = PileHandler.getPileHandlers(logger);
-        PileHandler.removePileHandlers(logger);
+        Logger logger = getRootLogger();
+
+        List<MomentaryHandler> momentaryHandlers =
+                MomentaryHandler.getMomentaryHandlers(logger);
+        MomentaryHandler.removeMomentaryHandlers(logger);
 
         logger.addHandler(newHandler);
 
-        for(PileHandler pileHandler : pileHandlers){
-            pileHandler.delegate(newHandler);
-            pileHandler.close();
+        for(MomentaryHandler momentaryHandler : momentaryHandlers){
+            momentaryHandler.transfer(newHandler);
+            momentaryHandler.close();
         }
 
         return;
diff --git a/src/main/java/jp/sfjp/jindolf/log/LogWrapper.java b/src/main/java/jp/sfjp/jindolf/log/LogWrapper.java
deleted file mode 100644 (file)
index 27c1d05..0000000
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * log wrapper
- *
- * License : The MIT License
- * Copyright(c) 2009 olyutorskii
- */
-
-package jp.sfjp.jindolf.log;
-
-import java.util.logging.Level;
-import java.util.logging.LogRecord;
-import java.util.logging.Logger;
-
-/**
- * 各種ログAPIへの共通ラッパー。
- * 現時点では java.util.logging のみサポート。
- */
-public class LogWrapper{
-
-    private final Logger jre14Logger;
-
-    /**
-     * コンストラクタ。
-     * @param logger ラップ対象のjava.util.loggingロガー
-     */
-    public LogWrapper(Logger logger){
-        super();
-        if(logger == null) throw new NullPointerException();
-        this.jre14Logger = logger;
-        return;
-    }
-
-    /**
-     * コンストラクタ。
-     * 新規生成された匿名ロガーを囲う。
-     */
-    public LogWrapper(){
-        this(Logger.getAnonymousLogger());
-        return;
-    }
-
-    /**
-     * ラップ対象のjava.util.loggingロガーを取得する。
-     * @return ラップ対象のjava.util.loggingロガー
-     */
-    public Logger getJre14Logger(){
-        return this.jre14Logger;
-    }
-
-    /**
-     * ログレコードにスタックトレース情報を埋め込む。
-     * @param record ログレコード
-     */
-    private void fillStackInfo(LogRecord record){
-        Thread selfThread = Thread.currentThread();
-        StackTraceElement[] stacks = selfThread.getStackTrace();
-
-        String thisName = this.getClass().getName();
-
-        boolean foundMySelf = false;
-        for(StackTraceElement frame : stacks){
-            String frameClassName = frame.getClassName();
-
-            if( ! foundMySelf && frameClassName.equals(thisName) ){
-                foundMySelf = true;
-                continue;
-            }
-
-            if( foundMySelf &&  ! frameClassName.equals(thisName) ){
-                record.setSourceClassName(frameClassName);
-                record.setSourceMethodName(frame.getMethodName());
-                break;
-            }
-        }
-
-        return;
-    }
-
-    /**
-     * java.util.loggingロガーへログ出力。
-     * @param level ログレベル
-     * @param msg メッセージ
-     */
-    private void logJre14(Level level, CharSequence msg){
-        logJre14(level, msg, null);
-        return;
-    }
-
-    /**
-     * java.util.loggingロガーへログ出力。
-     * @param level ログレベル
-     * @param msg メッセージ
-     * @param thrown 例外
-     */
-    private void logJre14(Level level, CharSequence msg, Throwable thrown){
-        String message;
-        if(msg == null) message = null;
-        else            message = msg.toString();
-
-        LogRecord record = new LogRecord(level, message);
-
-        if(thrown != null){
-            record.setThrown(thrown);
-        }
-
-        fillStackInfo(record);
-
-        this.jre14Logger.log(record);
-
-        return;
-    }
-
-    /**
-     * 単純な情報を出力する。
-     * @param msg メッセージ
-     */
-    public void info(CharSequence msg){
-        logJre14(Level.INFO, msg);
-        return;
-    }
-
-    /**
-     * 警告を出力する。
-     * @param msg メッセージ
-     */
-    public void warn(CharSequence msg){
-        warn(msg, null);
-        return;
-    }
-
-    /**
-     * 警告を出力する。
-     * @param msg メッセージ
-     * @param thrown 例外
-     */
-    public void warn(CharSequence msg, Throwable thrown){
-        logJre14(Level.WARNING, msg, thrown);
-        return;
-    }
-
-    /**
-     * 致命的な障害情報を出力する。
-     * @param msg メッセージ
-     */
-    public void fatal(CharSequence msg){
-        fatal(msg, null);
-        return;
-    }
-
-    /**
-     * 致命的な障害情報を出力する。
-     * @param msg メッセージ
-     * @param thrown 例外
-     */
-    public void fatal(CharSequence msg, Throwable thrown){
-        logJre14(Level.SEVERE, msg, thrown);
-        return;
-    }
-
-    // TODO Apache log4j サポート
-}
index af32849..cbd0e0f 100644 (file)
@@ -10,6 +10,8 @@ package jp.sfjp.jindolf.log;
 import java.awt.AWTEvent;
 import java.awt.EventQueue;
 import java.awt.Toolkit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 /**
  * 異常系をロギングするイベントディスパッチャ。
@@ -19,7 +21,7 @@ public class LoggingDispatcher extends EventQueue{
     private static final String FATALMSG =
             "イベントディスパッチ中に異常が起きました。";
 
-    private static final LogWrapper LOGGER = new LogWrapper();
+    private static final Logger LOGGER = Logger.getAnonymousLogger();
 
     /**
      * コンストラクタ。
@@ -46,7 +48,7 @@ public class LoggingDispatcher extends EventQueue{
      * @param e 例外
      */
     private void errlog(Throwable e){
-        LOGGER.fatal(FATALMSG, e);
+        LOGGER.log(Level.SEVERE, FATALMSG, e);
         return;
     }
 
@@ -1,5 +1,5 @@
 /*
- * Dummy logging handler
+ * momentary logging handler
  *
  * License : The MIT License
  * Copyright(c) 2008 olyutorskii
@@ -16,47 +16,52 @@ import java.util.logging.LogRecord;
 import java.util.logging.Logger;
 
 /**
- * なにもしないロギングハンドラ。
- * あとからなにがロギングされたのか一括して出力することができる。
+ * なにもしない一時的なロギングハンドラ。
+ * なにがロギングされたのかあとから一括して取得することができる。
+ * <p>知らないうちにメモリを圧迫しないよう注意。
  */
-public class PileHandler extends Handler{
+public class MomentaryHandler extends Handler{
 
-    private final List<LogRecord> logList = new LinkedList<LogRecord>();
+    private final List<LogRecord> logList =
+            Collections.synchronizedList(new LinkedList<LogRecord>());
     private final List<LogRecord> unmodList =
             Collections.unmodifiableList(this.logList);
 
+
     /**
-     * ã\83­ã\82®ã\83³ã\82°ã\83\8fã\83³ã\83\89ã\83©ã\82\92ç\94\9fæ\88\90ã\81\99ã\82\8b
+     * ã\82³ã\83³ã\82¹ã\83\88ã\83©ã\82¯ã\82¿
      */
-    public PileHandler(){
+    public MomentaryHandler(){
         super();
         return;
     }
 
+
     /**
-     * ロガーに含まれる{@link PileHandler}型ハンドラのリストを返す。
+     * ロガーに含まれる{@link MomentaryHandler}型ハンドラのリストを返す。
      * @param logger ロガー
-     * @return {@link PileHandler}型ハンドラのリスト
+     * @return {@link MomentaryHandler}型ハンドラのリスト
      */
-    public static List<PileHandler> getPileHandlers(Logger logger){
-        List<PileHandler> result = new LinkedList<PileHandler>();
+    public static List<MomentaryHandler>
+            getMomentaryHandlers(Logger logger){
+        List<MomentaryHandler> result = new LinkedList<MomentaryHandler>();
 
         for(Handler handler : logger.getHandlers()){
-            if( ! (handler instanceof PileHandler) ) continue;
-            PileHandler pileHandler = (PileHandler) handler;
-            result.add(pileHandler);
+            if( ! (handler instanceof MomentaryHandler) ) continue;
+            MomentaryHandler momentaryHandler = (MomentaryHandler) handler;
+            result.add(momentaryHandler);
         }
 
         return result;
     }
 
     /**
-     * ロガーに含まれる{@link PileHandler}型ハンドラを全て削除する。
+     * ロガーに含まれる{@link MomentaryHandler}型ハンドラを全て削除する。
      * @param logger ロガー
      */
-    public static void removePileHandlers(Logger logger){
-        for(PileHandler pileHandler : getPileHandlers(logger)){
-            logger.removeHandler(pileHandler);
+    public static void removeMomentaryHandlers(Logger logger){
+        for(MomentaryHandler handler : getMomentaryHandlers(logger)){
+            logger.removeHandler(handler);
         }
         return;
     }
@@ -64,7 +69,7 @@ public class PileHandler extends Handler{
     /**
      * 蓄積されたログレコードのリストを返す。
      * 古いログが先頭に来る。
-     * @return ログレコードのリスト。変更不可。
+     * @return 刻一刻と成長するログレコードのリスト。変更不可。
      */
     public List<LogRecord> getRecordList(){
         return this.unmodList;
@@ -83,6 +88,8 @@ public class PileHandler extends Handler{
             return;
         }
 
+        record.getSourceMethodName();
+
         this.logList.add(record);
 
         return;
@@ -99,6 +106,7 @@ public class PileHandler extends Handler{
 
     /**
      * {@inheritDoc}
+     * 以降のログ出力を無視する。
      */
     @Override
     public void close(){
@@ -108,11 +116,14 @@ public class PileHandler extends Handler{
     }
 
     /**
-     * 他のハンドラへ蓄積したログをまとめて出力する。
-     * 最後に自分自身をクローズし、蓄積されたログを解放する。
+     * 自分自身をクローズし、
+     * 蓄積したログを他のハンドラへまとめて出力する。
+     * 最後に蓄積されたログを解放する。
      * @param handler 他のハンドラ
+     * @throws NullPointerException 引数がnull
      */
-    public void delegate(Handler handler){
+    public void transfer(Handler handler) throws NullPointerException {
+        if(handler == null) throw new NullPointerException();
         if(handler == this) return;
 
         close();
index 5933331..c9686e7 100644 (file)
@@ -7,17 +7,22 @@
 
 package jp.sfjp.jindolf.log;
 
+import java.awt.EventQueue;
 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.AttributeSet;
 import javax.swing.text.BadLocationException;
 import javax.swing.text.Document;
 
 /**
- * Swingテキストコンポーネント用データモデル{@link javax.swing.text.Document}
+ * Swingテキストコンポーネント用データモデル
+ * {@link javax.swing.text.Document}
  * に出力する{@link java.util.logging.Handler}。
+ * <p>スレッド間競合はEDTで解決される。
+ * <p>一定の文字数を超えないよう、古い記録は消去される。
  */
 public class SwingDocHandler extends Handler{
 
@@ -48,6 +53,40 @@ public class SwingDocHandler extends Handler{
     }
 
     /**
+     * ドキュメント末尾に文字列を追加する。
+     * <p>EDTから呼ばなければならない。
+     * @param logMessage 文字列
+     */
+    private void appendLog(String logMessage){
+        try{
+            this.document.insertString(this.document.getLength(),
+                                       logMessage,
+                                       (AttributeSet) null );
+        }catch(BadLocationException e){
+            assert false;
+        }
+        return;
+    }
+
+    /**
+     * ドキュメント先頭部をチョップして最大長に納める。
+     * <p>EDTから呼ばなければならない。
+     */
+    private void chopHead(){
+        int docLength = this.document.getLength();
+        if(docLength <= DOCLIMIT) return;
+
+        int offset = docLength - CHOPPEDLEN;
+        try{
+            this.document.remove(0, offset);
+        }catch(BadLocationException e){
+            assert false;
+        }
+
+        return;
+    }
+
+    /**
      * {@inheritDoc}
      * @param record {@inheritDoc}
      */
@@ -56,25 +95,16 @@ public class SwingDocHandler extends Handler{
         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;
-        }
+        final String message = formatter.format(record);
 
-        int docLength = this.document.getLength();
-        if(docLength > DOCLIMIT){
-            int offset = docLength - CHOPPEDLEN;
-            try{
-                this.document.remove(0, offset);
-            }catch(BadLocationException e){
-                assert false;
+        EventQueue.invokeLater(new Runnable(){
+            public void run(){
+                appendLog(message);
+                chopHead();
             }
-        }
+        });
 
         return;
     }
index 993efbc..623339a 100644 (file)
@@ -10,7 +10,7 @@ package jp.sfjp.jindolf.net;
 import java.io.IOException;
 import java.net.HttpURLConnection;
 import java.net.URLConnection;
-import java.text.NumberFormat;
+import java.text.MessageFormat;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import jp.sfjp.jindolf.VerInfo;
@@ -35,17 +35,9 @@ public final class HttpUtils{
     private static final Pattern MTYPE_PATTERN = Pattern.compile(MTYPE_REGEX);
     private static final Pattern ATTR_PATTERN  = Pattern.compile(PARAM_REGEX);
 
-    private static final NumberFormat THROUGHPUT_FORMAT;
-    private static final NumberFormat SIZE_FORMAT;
-
-    static{
-        THROUGHPUT_FORMAT = NumberFormat.getInstance();
-        THROUGHPUT_FORMAT.setMaximumFractionDigits(1);
-        THROUGHPUT_FORMAT.setMinimumFractionDigits(1);
-        THROUGHPUT_FORMAT.setGroupingUsed(true);
-        SIZE_FORMAT = NumberFormat.getInstance();
-        SIZE_FORMAT.setGroupingUsed(true);
-    }
+    private static final String THROUGHPUT_FORM =
+            "{0,number,#,##0}Bytes {1,number,#,##0.0}{2}Bytes/sec";
+    private static final String HTTP_FORM = "{0} {1} [{2} {3}] {4}";
 
 
     /**
@@ -62,7 +54,7 @@ public final class HttpUtils{
      * ネットワークのスループット報告用文字列を生成する。
      * @param size 転送サイズ(バイト数)
      * @param nano 所要時間(ナノ秒)
-     * @return スループット文字列
+     * @return スループット文字列。引数のいずれかが0以下なら空文字列
      */
     public static String throughput(long size, long nano){
         if(size <= 0 || nano <= 0) return "";
@@ -80,9 +72,9 @@ public final class HttpUtils{
             unit = "M";
         }
 
-        String result =  SIZE_FORMAT.format(size) + "Bytes "
-                       + THROUGHPUT_FORMAT.format(rate) + unit
-                       + "Bytes/sec";
+        String result =
+                  MessageFormat.format(THROUGHPUT_FORM, size, rate, unit);
+
         return result;
     }
 
@@ -91,11 +83,11 @@ public final class HttpUtils{
      * @param conn HTTPコネクション
      * @param size 転送サイズ
      * @param nano 転送に要したナノ秒
-     * @return セッション結果
+     * @return セッション結果文字列。
      */
     public static String formatHttpStat(HttpURLConnection conn,
-                                          long size,
-                                          long nano ){
+                                        long size,
+                                        long nano ){
         String method = conn.getRequestMethod();
         String url    = conn.getURL().toString();
 
@@ -115,13 +107,13 @@ public final class HttpUtils{
 
         String throughput = throughput(size, nano);
 
-        String message =  method
-                        + " " + url
-                        + " [" + responseCode
-                        + " " + responseMessage + "]"
-                        + " " + throughput;
+        String result;
+        result = MessageFormat.format(HTTP_FORM,
+                                      method, url,
+                                      responseCode, responseMessage,
+                                      throughput );
 
-        return message;
+        return result;
     }
 
     /**
index cf39785..f69add9 100644 (file)
@@ -22,10 +22,10 @@ import java.nio.charset.Charset;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.logging.Logger;
 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;
@@ -44,7 +44,7 @@ public class ServerAccess{
     private static final
             Map<String, SoftReference<BufferedImage>> IMAGE_CACHE;
 
-    private static final LogWrapper LOGGER = new LogWrapper();
+    private static final Logger LOGGER = Logger.getAnonymousLogger();
 
     static{
         Map<String, SoftReference<BufferedImage>> cache =
@@ -281,7 +281,7 @@ public class ServerAccess{
         if(responseCode != HttpURLConnection.HTTP_OK){ // 200
             String logMessage =  "発言のダウンロードに失敗しました。";
             logMessage += HttpUtils.formatHttpStat(connection, 0, 0);
-            LOGGER.warn(logMessage);
+            LOGGER.warning(logMessage);
             return null;
         }
 
@@ -336,7 +336,7 @@ public class ServerAccess{
         if(responseCode != HttpURLConnection.HTTP_OK){
             String logMessage =  "イメージのダウンロードに失敗しました。";
             logMessage += HttpUtils.formatHttpStat(connection, 0, 0);
-            LOGGER.warn(logMessage);
+            LOGGER.warning(logMessage);
             return null;
         }
 
@@ -381,7 +381,7 @@ public class ServerAccess{
         int responseCode = connection.getResponseCode();
         if(responseCode != HttpURLConnection.HTTP_MOVED_TEMP){    // 302
             String logMessage =  "認証情報の送信に失敗しました。";
-            LOGGER.warn(logMessage);
+            LOGGER.warning(logMessage);
             connection.disconnect();
             return false;
         }
index 0ab55aa..09fd583 100644 (file)
@@ -11,7 +11,7 @@ import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.HttpURLConnection;
-import jp.sfjp.jindolf.log.LogWrapper;
+import java.util.logging.Logger;
 
 /**
  * 読み込みバイト数を記録するHTTPコネクション由来のInputStream。
@@ -21,7 +21,7 @@ public class TallyInputStream extends InputStream{
 
     private static final int BUFSIZE = 2 * 1024;
 
-    private static final LogWrapper LOGGER = new LogWrapper();
+    private static final Logger LOGGER = Logger.getAnonymousLogger();
 
 
     private final HttpURLConnection conn;
index 1bb7627..e0a9b1e 100644 (file)
@@ -11,7 +11,7 @@ import java.io.BufferedOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.net.HttpURLConnection;
-import jp.sfjp.jindolf.log.LogWrapper;
+import java.util.logging.Logger;
 
 /**
  * 書き込みバイト数をログ出力するHTTPコネクション由来のOutputStream。
@@ -20,7 +20,7 @@ public class TallyOutputStream extends OutputStream{
 
     private static final int BUFSIZE = 512;
 
-    private static final LogWrapper LOGGER = new LogWrapper();
+    private static final Logger LOGGER = Logger.getAnonymousLogger();
 
 
     private final HttpURLConnection conn;
index 08e133f..800aec2 100644 (file)
@@ -56,7 +56,7 @@ import jp.sourceforge.jindolf.corelib.GameRole;
 import jp.sourceforge.jindolf.corelib.Team;
 
 /**
- * æ±ºç\9d\80ã\81®ã\81¤ã\81\84ã\81\9fæ\9d\91ã\81®ã\82µã\83\9eã\83ªを表示する。
+ * æ±ºç\9d\80ã\81®ã\81¤ã\81\84ã\81\9fæ\9d\91ã\81®ã\83\80ã\82¤ã\82¸ã\82§ã\82¹ã\83\88を表示する。
  */
 @SuppressWarnings("serial")
 public class VillageDigest
index 74492ec..4ec5f82 100644 (file)
@@ -20,6 +20,7 @@ import java.awt.image.BufferedImage;
 import java.awt.image.BufferedImageOp;
 import java.awt.image.ColorConvertOp;
 import java.lang.reflect.InvocationTargetException;
+import java.util.logging.Logger;
 import javax.swing.BorderFactory;
 import javax.swing.Icon;
 import javax.swing.JComponent;
@@ -27,7 +28,6 @@ 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関連のユーティリティクラス。
@@ -58,7 +58,7 @@ public final class GUIUtils{
         public void run(){}
     };
 
-    private static final LogWrapper LOGGER = new LogWrapper();
+    private static final Logger LOGGER = Logger.getAnonymousLogger();
 
     static{
         HINTS_QUALITY = new RenderingHints(null);
@@ -145,7 +145,7 @@ public final class GUIUtils{
         BufferedImage image =
                 ResourceManager.getBufferedImage(RES_LOGOICON);
         if(image == null){
-            LOGGER.warn("ロゴイメージの取得に失敗しました");
+            LOGGER.warning("ロゴイメージの取得に失敗しました");
             image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
             // TODO デカく "狼" とでも描くか?
         }
@@ -167,7 +167,7 @@ public final class GUIUtils{
         BufferedImage image =
                 ResourceManager.getBufferedImage(RES_WINDOWICON);
         if(image == null){
-            LOGGER.warn("アイコンイメージの取得に失敗しました");
+            LOGGER.warning("アイコンイメージの取得に失敗しました");
             image = getLogoImage();
         }
 
index 04ab2cc..27c991d 100644 (file)
@@ -20,6 +20,8 @@ import java.awt.event.ItemListener;
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import javax.swing.BorderFactory;
 import javax.swing.JButton;
 import javax.swing.JComboBox;
@@ -37,7 +39,6 @@ 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;
@@ -51,7 +52,7 @@ public class AccountPanel
         extends JDialog
         implements ActionListener, ItemListener{
 
-    private static final LogWrapper LOGGER = new LogWrapper();
+    private static final Logger LOGGER = Logger.getAnonymousLogger();
 
 
     private final Map<Land, String> landUserIDMap =
@@ -70,24 +71,12 @@ public class AccountPanel
     /**
      * アカウントパネルを生成。
      * @param owner フレームオーナー
-     * @param landsModel 国モデル
-     * @throws java.lang.NullPointerException 引数がnull
      */
     @SuppressWarnings("LeakingThisInConstructor")
-    public AccountPanel(Frame owner, LandsModel landsModel)
-            throws NullPointerException{
+    public AccountPanel(Frame owner){
         super(owner);
         setModal(true);
 
-        if(landsModel == null) throw new NullPointerException();
-        for(Land land : landsModel.getLandList()){
-            String userID = "";
-            char[] password = {};
-            this.landUserIDMap.put(land, userID);
-            this.landPasswordMap.put(land, password);
-            this.landBox.addItem(land);
-        }
-
         GUIUtils.modifyWindowAttributes(this, true, false, true);
 
         this.landBox.setToolTipText("アカウント管理する国を選ぶ");
@@ -148,10 +137,6 @@ public class AccountPanel
 
         content.add(buttonPanel, constraints);
 
-        preSelectActiveLand();
-
-        updateGUI();
-
         return;
     }
 
@@ -277,7 +262,7 @@ public class AccountPanel
      * @param e ネットワークエラー
      */
     protected void showNetworkError(IOException e){
-        LOGGER.warn(
+        LOGGER.log(Level.WARNING,
                 "アカウント処理中にネットワークのトラブルが発生しました", e);
 
         Land land = getSelectedLand();
@@ -389,6 +374,8 @@ public class AccountPanel
      */
     private void updateGUI(){
         Land land = getSelectedLand();
+        if(land == null) return;
+
         LandState state = land.getLandDef().getLandState();
         ServerAccess server = land.getServerAccess();
         boolean hasLoggedIn = server.hasLoggedIn();
@@ -420,6 +407,32 @@ public class AccountPanel
     }
 
     /**
+     * 国情報を設定する。
+     * @param model 国情報
+     * @throws NullPointerException 引数がnull
+     */
+    public void setModel(LandsModel model) throws NullPointerException{
+        if(model == null) throw new NullPointerException();
+
+        this.landUserIDMap.clear();
+        this.landPasswordMap.clear();
+        this.landBox.removeAllItems();
+
+        for(Land land : model.getLandList()){
+            String userID = "";
+            char[] password = {};
+            this.landUserIDMap.put(land, userID);
+            this.landPasswordMap.put(land, password);
+            this.landBox.addItem(land);
+        }
+
+        preSelectActiveLand();
+        updateGUI();
+
+        return;
+    }
+
+    /**
      * {@inheritDoc}
      * ボタン操作のリスナ。
      * @param event イベント {@inheritDoc}
index 8cfeb06..9162f5a 100644 (file)
@@ -17,6 +17,8 @@ import java.awt.event.WindowAdapter;
 import java.awt.event.WindowEvent;
 import java.io.IOException;
 import java.net.URL;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import javax.swing.BorderFactory;
 import javax.swing.JButton;
 import javax.swing.JEditorPane;
@@ -33,7 +35,6 @@ 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;
 
 /**
@@ -45,7 +46,7 @@ public class HelpFrame extends JFrame
 
     private static final String HELP_HTML = "resources/html/help.html";
 
-    private static final LogWrapper LOGGER = new LogWrapper();
+    private static final Logger LOGGER = Logger.getAnonymousLogger();
 
 
     private final JTabbedPane tabPanel = new JTabbedPane();
@@ -55,11 +56,9 @@ public class HelpFrame extends JFrame
 
     /**
      * コンストラクタ。
-     * @param optinfo コマンドラインオプション
-     * @param configStore 設定ディレクトリ情報
      */
     @SuppressWarnings("LeakingThisInConstructor")
-    public HelpFrame(OptionInfo optinfo, ConfigStore configStore){
+    public HelpFrame(){
         super();
 
         GUIUtils.modifyWindowAttributes(this, true, false, true);
@@ -92,24 +91,6 @@ public class HelpFrame extends JFrame
         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());
-
-        if(configStore.useStoreFile()){
-            info.append("設定格納ディレクトリ : ")
-                .append(configStore.getConfigPath().getPath());
-        }else{
-            info.append("※ 設定格納ディレクトリは使っていません。");
-        }
-        this.vmInfo.setText(info.toString());
-
         design();
 
         return;
@@ -166,7 +147,7 @@ public class HelpFrame extends JFrame
         try{
             this.htmlView.setPage(url);
         }catch(IOException e){
-            LOGGER.warn("ヘルプファイルが読み込めません", e);
+            LOGGER.log(Level.WARNING, "ヘルプファイルが読み込めません", e);
             assert false;
         }
 
@@ -174,6 +155,34 @@ public class HelpFrame extends JFrame
     }
 
     /**
+     * 実行環境に関する情報を更新する。
+     * @param optinfo コマンドライン引数情報
+     * @param configStore 設定ファイル情報
+     */
+    public void updateVmInfo(OptionInfo optinfo, ConfigStore configStore){
+        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());
+
+        if(configStore.useStoreFile()){
+            info.append("設定格納ディレクトリ : ")
+                .append(configStore.getConfigPath().getPath());
+        }else{
+            info.append("※ 設定格納ディレクトリは使っていません。");
+        }
+
+        this.vmInfo.setText(info.toString());
+
+        return;
+    }
+
+    /**
      * {@inheritDoc}
      * 閉じるボタン押下処理。
      * @param event ボタン押下イベント {@inheritDoc}
diff --git a/src/main/java/jp/sfjp/jindolf/view/TopFrame.java b/src/main/java/jp/sfjp/jindolf/view/TopFrame.java
new file mode 100644 (file)
index 0000000..dc0b2aa
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Top frame
+ *
+ * License : The MIT License
+ * Copyright(c) 2012 olyutorskii
+ */
+
+package jp.sfjp.jindolf.view;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.LayoutManager;
+import java.awt.event.KeyAdapter;
+import java.awt.event.MouseAdapter;
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+
+/**
+ * メインアプリウィンドウ。
+ * {@link TopView}をウィンドウ表示するための皮。
+ */
+@SuppressWarnings("serial")
+public class TopFrame extends JFrame{
+
+    private final TopView topView = new TopView();
+
+    /**
+     * コンストラクタ。
+     */
+    public TopFrame(){
+        super();
+
+        Container content = getContentPane();
+        design(content);
+
+        modifyGrassPane();
+
+        return;
+    }
+
+    /**
+     * レイアウトをデザインする。
+     * @param container コンテナ
+     */
+    private void design(Container container){
+        LayoutManager layout = new BorderLayout();
+        container.setLayout(layout);
+        container.add(this.topView, BorderLayout.CENTER);
+
+        return;
+    }
+
+    /**
+     * グラスペインのカスタマイズを行う。
+     */
+    private void modifyGrassPane(){
+        Component glassPane = new JComponent() {};
+
+        glassPane.addMouseListener(new MouseAdapter() {});
+        glassPane.addKeyListener(new KeyAdapter() {});
+
+        setGlassPane(glassPane);
+
+        return;
+    }
+
+    /**
+     * トップビューを返す。
+     * @return トップビュー
+     */
+    public TopView getTopView(){
+        return this.topView;
+    }
+
+}
index 423375e..5f0ad2c 100644 (file)
@@ -54,7 +54,7 @@ public class TopView extends JPanel{
 
     private final LandInfoPanel landInfo = new LandInfoPanel();
 
-    private final JTextField sysMessage = new JTextField();
+    private final JTextField sysMessage = new JTextField("");
     private final JProgressBar progressBar = new JProgressBar();
 
     private final TabBrowser tabBrowser = new TabBrowser();
@@ -263,16 +263,24 @@ public class TopView extends JPanel{
      * @param message 更新文字列
      */
     public void updateSysMessage(String message){
-        if(message == null) return;
-        String text;
-        if(message.length() <= 0) text = " ";
-        else                      text = message;
+        String text = message;
+        if(message == null) text = "";
         this.sysMessage.setText(text);   // Thread safe
         GUIUtils.dispatchEmptyAWTEvent();
         return;
     }
 
     /**
+     * ステータスバー文字列を返す。
+     * @return ステータスバー文字列
+     */
+    public String getSysMessage(){
+        String result = this.sysMessage.getText();
+        if(result == null) result = "";
+        return result;
+    }
+
+    /**
      * 初期パネルを表示する。
      */
     public void showInitPanel(){
diff --git a/src/main/java/jp/sfjp/jindolf/view/WindowManager.java b/src/main/java/jp/sfjp/jindolf/view/WindowManager.java
new file mode 100644 (file)
index 0000000..1bbf8c3
--- /dev/null
@@ -0,0 +1,439 @@
+/*
+ * window manager
+ *
+ * License : The MIT License
+ * Copyright(c) 2012 olyutorskii
+ */
+
+package jp.sfjp.jindolf.view;
+
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Frame;
+import java.awt.Window;
+import java.util.LinkedList;
+import java.util.List;
+import javax.swing.JComponent;
+import javax.swing.JMenu;
+import javax.swing.JPopupMenu;
+import jp.sfjp.jindolf.VerInfo;
+import jp.sfjp.jindolf.editor.TalkPreview;
+import jp.sfjp.jindolf.log.LogFrame;
+import jp.sfjp.jindolf.summary.DaySummary;
+import jp.sfjp.jindolf.summary.VillageDigest;
+
+/**
+ * ウィンドウ群の管理を行う。
+ */
+public class WindowManager {
+
+    private static final String TITLE_FILTER =
+            getFrameTitle("発言フィルタ");
+    private static final String TITLE_LOGGER =
+            getFrameTitle("ログ表示");
+    private static final String TITLE_EDITOR =
+            getFrameTitle("発言エディタ");
+    private static final String TITLE_OPTION =
+            getFrameTitle("オプション設定");
+    private static final String TITLE_FIND =
+            getFrameTitle("発言検索");
+    private static final String TITLE_ACCOUNT =
+            getFrameTitle("アカウント管理");
+    private static final String TITLE_DIGEST =
+            getFrameTitle("村のダイジェスト");
+    private static final String TITLE_DAYSUMMARY =
+            getFrameTitle("発言集計");
+    private static final String TITLE_HELP =
+            getFrameTitle("ヘルプ");
+
+    private static final Frame NULLPARENT = null;
+
+
+    private FilterPanel filterPanel;
+    private LogFrame logFrame;
+    private TalkPreview talkPreview;
+    private OptionPanel optionPanel;
+    private FindPanel findPanel;
+    private AccountPanel accountPanel;
+    private VillageDigest villageDigest;
+    private DaySummary daySummary;
+    private HelpFrame helpFrame;
+    private TopFrame topFrame;
+
+    private final List<Window> windowSet = new LinkedList<Window>();
+
+
+    /**
+     * コンストラクタ。
+     */
+    public WindowManager(){
+        super();
+        return;
+    }
+
+
+    /**
+     * ウィンドウタイトルに前置詞をつける。
+     * @param text 元タイトル
+     * @return タイトル文字列
+     */
+    private static String getFrameTitle(String text){
+        String result = VerInfo.getFrameTitle(text);
+        return result;
+    }
+
+
+    /**
+     * 発言フィルタウィンドウを生成する。
+     * @return 発言フィルタウィンドウ
+     */
+    protected FilterPanel createFilterPanel(){
+        FilterPanel result;
+
+        result = new FilterPanel(NULLPARENT);
+        result.setTitle(TITLE_FILTER);
+        result.pack();
+        result.setVisible(false);
+
+        this.windowSet.add(result);
+
+        return result;
+    }
+
+    /**
+     * 発言フィルタウィンドウを返す。
+     * @return 発言フィルタウィンドウ
+     */
+    public FilterPanel getFilterPanel(){
+        if(this.filterPanel == null){
+            this.filterPanel = createFilterPanel();
+        }
+        return this.filterPanel;
+    }
+
+    /**
+     * ログウィンドウを生成する。
+     * @return ログウィンドウ
+     */
+    protected LogFrame createLogFrame(){
+        LogFrame result;
+
+        result = new LogFrame(NULLPARENT);
+        result.setTitle(TITLE_LOGGER);
+        result.pack();
+        result.setSize(600, 500);
+        result.setLocationByPlatform(true);
+        result.setVisible(false);
+
+        this.windowSet.add(result);
+
+        return result;
+    }
+
+    /**
+     * ログウィンドウを返す。
+     * @return ログウィンドウ
+     */
+    public LogFrame getLogFrame(){
+        if(this.logFrame == null){
+            this.logFrame = createLogFrame();
+        }
+        return this.logFrame;
+    }
+
+    /**
+     * 発言エディタウィンドウを生成する。
+     * @return 発言エディタウィンドウ
+     */
+    protected TalkPreview createTalkPreview(){
+        TalkPreview result;
+
+        result = new TalkPreview();
+        result.setTitle(TITLE_EDITOR);
+        result.pack();
+        result.setSize(700, 500);
+        result.setVisible(false);
+
+        this.windowSet.add(result);
+
+        return result;
+    }
+
+    /**
+     * 発言エディタウィンドウを返す。
+     * @return 発言エディタウィンドウ
+     */
+    public TalkPreview getTalkPreview(){
+        if(this.talkPreview == null){
+            this.talkPreview = createTalkPreview();
+        }
+        return this.talkPreview;
+    }
+
+    /**
+     * オプション設定ウィンドウを生成する。
+     * @return オプション設定ウィンドウ
+     */
+    protected OptionPanel createOptionPanel(){
+        OptionPanel result;
+
+        result = new OptionPanel(NULLPARENT);
+        result.setTitle(TITLE_OPTION);
+        result.pack();
+        result.setSize(450, 500);
+        result.setVisible(false);
+
+        this.windowSet.add(result);
+
+        return result;
+    }
+
+    /**
+     * オプション設定ウィンドウを返す。
+     * @return オプション設定ウィンドウ
+     */
+    public OptionPanel getOptionPanel(){
+        if(this.optionPanel == null){
+            this.optionPanel = createOptionPanel();
+        }
+        return this.optionPanel;
+    }
+
+    /**
+     * 検索ウィンドウを生成する。
+     * @return 検索ウィンドウ
+     */
+    protected FindPanel createFindPanel(){
+        FindPanel result;
+
+        result = new FindPanel(NULLPARENT);
+        result.setTitle(TITLE_FIND);
+        result.pack();
+        result.setVisible(false);
+
+        this.windowSet.add(result);
+
+        return result;
+    }
+
+    /**
+     * 検索ウィンドウを返す。
+     * @return 検索ウィンドウ
+     */
+    public FindPanel getFindPanel(){
+        if(this.findPanel == null){
+            this.findPanel = createFindPanel();
+        }
+        return this.findPanel;
+    }
+
+    /**
+     * ログインウィンドウを生成する。
+     * @return ログインウィンドウ
+     */
+    protected AccountPanel createAccountPanel(){
+        AccountPanel result;
+
+        result = new AccountPanel(NULLPARENT);
+        result.setTitle(TITLE_ACCOUNT);
+        result.pack();
+        result.setVisible(false);
+
+        this.windowSet.add(result);
+
+        return result;
+    }
+
+    /**
+     * ログインウィンドウを返す。
+     * @return ログインウィンドウ
+     */
+    public AccountPanel getAccountPanel(){
+        if(this.accountPanel == null){
+            this.accountPanel = createAccountPanel();
+        }
+        return this.accountPanel;
+    }
+
+    /**
+     * 村ダイジェストウィンドウを生成する。
+     * @return 村ダイジェストウィンドウ
+     */
+    protected VillageDigest createVillageDigest(){
+        VillageDigest result;
+
+        result = new VillageDigest(NULLPARENT);
+        result.setTitle(TITLE_DIGEST);
+        result.pack();
+        result.setSize(600, 550);
+        result.setVisible(false);
+
+        this.windowSet.add(result);
+
+        return result;
+    }
+
+    /**
+     * 村ダイジェストウィンドウを返す。
+     * @return 村ダイジェストウィンドウ
+     */
+    public VillageDigest getVillageDigest(){
+        if(this.villageDigest == null){
+            this.villageDigest = createVillageDigest();
+        }
+        return this.villageDigest;
+    }
+
+    /**
+     * 発言集計ウィンドウを生成する。
+     * @return 発言集計ウィンドウ
+     */
+    protected DaySummary createDaySummary(){
+        DaySummary result;
+
+        result = new DaySummary(NULLPARENT);
+        result.setTitle(TITLE_DAYSUMMARY);
+        result.pack();
+        result.setSize(400, 500);
+        result.setVisible(false);
+
+        this.windowSet.add(result);
+
+        return result;
+    }
+
+    /**
+     * 発言集計ウィンドウを返す。
+     * @return 発言集計ウィンドウ
+     */
+    public DaySummary getDaySummary(){
+        if(this.daySummary == null){
+            this.daySummary = createDaySummary();
+        }
+        return this.daySummary;
+    }
+
+    /**
+     * ヘルプウィンドウを生成する。
+     * @return ヘルプウィンドウ
+     */
+    protected HelpFrame createHelpFrame(){
+        HelpFrame result;
+
+        result = new HelpFrame();
+        result.setTitle(TITLE_HELP);
+        result.pack();
+        result.setSize(450, 450);
+        result.setVisible(false);
+
+        this.windowSet.add(result);
+
+        return result;
+    }
+
+    /**
+     * ヘルプウィンドウを返す。
+     * @return ヘルプウィンドウ
+     */
+    public HelpFrame getHelpFrame(){
+        if(this.helpFrame == null){
+            this.helpFrame = createHelpFrame();
+        }
+        return this.helpFrame;
+    }
+
+    /**
+     * トップフレームを生成する。
+     * @return トップフレーム
+     */
+    protected TopFrame createTopFrame(){
+        TopFrame result = new TopFrame();
+        result.setVisible(false);
+        this.windowSet.add(result);
+        return result;
+    }
+
+    /**
+     * トップフレームを返す。
+     * @return トップフレーム
+     */
+    public TopFrame getTopFrame(){
+        if(this.topFrame == null){
+            this.topFrame = createTopFrame();
+        }
+        return this.topFrame;
+    }
+
+    /**
+     * 管理下にある全ウィンドウのLookAndFeelを更新する。
+     * 必要に応じて再パッキングが行われる。
+     */
+    public void changeAllWindowUI(){
+        for(Window window : this.windowSet){
+            updateTreeUI(window);
+        }
+
+        if(this.filterPanel  != null) this.filterPanel.pack();
+        if(this.findPanel    != null) this.findPanel.pack();
+        if(this.accountPanel != null) this.accountPanel.pack();
+
+        return;
+    }
+
+    /**
+     * 再帰的に下層コンポーネントのLaFを更新する。
+     * <p>{@link javax.swing.SwingUtilities#updateComponentTreeUI(Component)}
+     * がポップアップメニューのLaF更新を正しく行わないSun製JREのバグ
+     * [BugID:6299213]
+     * を回避するために作られた。
+     * @param comp 開始コンポーネント
+     * @see <a href="http://bugs.sun.com/view_bug.do?bug_id=6299213">
+     * BugID:6299213
+     * </a>
+     */
+    public static void updateTreeUI(Component comp) {
+        updateTreeUI(comp, true);
+        return;
+    }
+
+    /**
+     * 再帰的に下層コンポーネントのLaFを更新する。
+     * @param comp 開始コンポーネント
+     * @param isRoot このコンポーネントが最上位か否か指定する。
+     * trueが指定された場合、LaF更新作業の後に再レイアウトを促す。
+     * パフォーマンスの観点から、ポップアップ以外の下層コンポーネントには
+     * 必要のない限りfalse指定を推奨。
+     */
+    public static void updateTreeUI(Component comp, boolean isRoot) {
+        if(comp instanceof JComponent){
+            JComponent jcomp = (JComponent)comp;
+            jcomp.updateUI();
+
+            JPopupMenu popup = jcomp.getComponentPopupMenu();
+            if(popup != null){
+                updateTreeUI(popup, true);
+            }
+        }
+
+        if(comp instanceof JMenu){
+            JMenu menu = (JMenu)comp;
+            for(Component child : menu.getMenuComponents()){
+                updateTreeUI(child, false);
+            }
+        }else if(comp instanceof Container){
+            Container cont = (Container)comp;
+            for(Component child : cont.getComponents()){
+                updateTreeUI(child, false);
+            }
+        }
+
+        if(isRoot){
+            comp.invalidate();
+            comp.validate();
+            comp.repaint();
+        }
+
+        return;
+    }
+
+}
diff --git a/src/test/java/jp/sfjp/jindolf/JreCheckerTest.java b/src/test/java/jp/sfjp/jindolf/JreCheckerTest.java
new file mode 100644 (file)
index 0000000..fa36a74
--- /dev/null
@@ -0,0 +1,223 @@
+/*
+ * JreChecker test
+ *
+ * Copyright 2012 olyutorskii
+ */
+
+package jp.sfjp.jindolf;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import org.junit.BeforeClass;
+
+/**
+ *
+ */
+public class JreCheckerTest {
+
+    public JreCheckerTest() {
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception {
+    }
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    /**
+     * Test of hasClass method, of class JreChecker.
+     */
+    @Test
+    public void testHasClass() {
+        System.out.println("hasClass");
+
+        assertTrue(JreChecker.hasClass("java.lang.Object"));
+        assertTrue(JreChecker.hasClass(this.getClass().getName()));
+        assertFalse(JreChecker.hasClass("x.x.X"));
+
+        return;
+    }
+
+    /**
+     * Test of has11Runtime method, of class JreChecker.
+     */
+    @Test
+    public void testHas11Runtime() {
+        System.out.println("has11Runtime");
+        assertTrue(JreChecker.has11Runtime());
+        return;
+    }
+
+    /**
+     * Test of has12Runtime method, of class JreChecker.
+     */
+    @Test
+    public void testHas12Runtime() {
+        System.out.println("has12Runtime");
+        assertTrue(JreChecker.has12Runtime());
+        return;
+    }
+
+    /**
+     * Test of has13Runtime method, of class JreChecker.
+     */
+    @Test
+    public void testHas13Runtime() {
+        System.out.println("has13Runtime");
+        assertTrue(JreChecker.has13Runtime());
+        return;
+    }
+
+    /**
+     * Test of has14Runtime method, of class JreChecker.
+     */
+    @Test
+    public void testHas14Runtime() {
+        System.out.println("has14Runtime");
+        assertTrue(JreChecker.has14Runtime());
+        return;
+    }
+
+    /**
+     * Test of has15Runtime method, of class JreChecker.
+     */
+    @Test
+    public void testHas15Runtime() {
+        System.out.println("has15Runtime");
+        assertTrue(JreChecker.has15Runtime());
+        return;
+    }
+
+    /**
+     * Test of has16Runtime method, of class JreChecker.
+     */
+    @Test
+    public void testHas16Runtime() {
+        System.out.println("has16Runtime");
+
+        boolean result = JreChecker.has16Runtime();
+
+        return;
+    }
+
+    /**
+     * Test of getLangPkgSpec method, of class JreChecker.
+     */
+    @Test
+    public void testGetLangPkgSpec() {
+        System.out.println("getLangPkgSpec");
+
+        String result = JreChecker.getLangPkgSpec();
+
+        return;
+    }
+
+    /**
+     * Test of getJreHome method, of class JreChecker.
+     */
+    @Test
+    public void testGetJreHome() {
+        System.out.println("getJreHome");
+
+        String result = JreChecker.getJreHome();
+
+        return;
+    }
+
+    /**
+     * Test of buildErrMessage method, of class JreChecker.
+     */
+    @Test
+    public void testBuildErrMessage() {
+        System.out.println("buildErrMessage");
+
+        String result = JreChecker.buildErrMessage();
+
+        return;
+    }
+
+    /**
+     * Test of alignLine method, of class JreChecker.
+     */
+    @Test
+    public void testAlignLine() {
+        System.out.println("alignLine");
+
+        String result;
+
+        result = JreChecker.alignLine("abc", 1);
+        assertEquals("a\nb\nc", result);
+
+        result = JreChecker.alignLine("abc", 2);
+        assertEquals("ab\nc", result);
+
+        result = JreChecker.alignLine("abc", 3);
+        assertEquals("abc", result);
+
+        result = JreChecker.alignLine("abc", 4);
+        assertEquals("abc", result);
+
+        result = JreChecker.alignLine("abc", 0);
+        assertEquals("\na\nb\nc", result);
+
+        result = JreChecker.alignLine("abc", -1);
+        assertEquals("\na\nb\nc", result);
+
+        result = JreChecker.alignLine("a\nbcde", 3);
+        assertEquals("a\nbcd\ne", result);
+
+        result = JreChecker.alignLine("", 3);
+        assertEquals("", result);
+
+        try{
+            JreChecker.alignLine(null, 3);
+            fail();
+        }catch(NullPointerException e){
+            assert true;
+        }
+
+        return;
+    }
+
+    /**
+     * Test of checkJre method, of class JreChecker.
+     */
+    @Test
+    public void testCheckJre() {
+        System.out.println("checkJre");
+
+        if(false){
+            JreChecker.checkJre();
+        }
+
+        return;
+    }
+
+    /**
+     * Test of showErrorDialog method, of class JreChecker.
+     */
+    @Test
+    public void testShowErrorDialog() {
+        System.out.println("showErrorDialog");
+
+        if(false){
+            JreChecker.showErrorDialog("abc");
+        }
+
+        return;
+    }
+
+}
index a99e3ff..b4f97a2 100644 (file)
@@ -1,11 +1,14 @@
 /*
  * HttpUtils Test
  *
- * Copyright(c) 2009 olyutorskii
+ * Copyright(c) 2012 olyutorskii
  */
 
 package jp.sfjp.jindolf.net;
 
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
 import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.Before;
@@ -39,25 +42,6 @@ public class HttpUtilsTest {
     }
 
     /**
-     * Test of getHTMLCharset method, of class HttpUtils.
-     */
-    @Test
-    public void getHTMLCharset() {
-        System.out.println("getHTMLCharset");
-        String contentType;
-
-        contentType = "text/html;charset = Shift_JIS";
-        String result = HttpUtils.getHTMLCharset(contentType);
-        assertEquals("Shift_JIS", result);
-
-        contentType = "text/html ; charset=Shift_JIS ; a = b  ; d=\"xyz\"  ";
-        result = HttpUtils.getHTMLCharset(contentType);
-        assertEquals("Shift_JIS", result);
-
-        return;
-    }
-
-    /**
      * Test of escapeHttpComment method, of class HttpUtils.
      */
     @Test
@@ -90,4 +74,193 @@ public class HttpUtilsTest {
 
         return;
     }
+
+    /**
+     * Test of throughput method, of class HttpUtils.
+     */
+    @Test
+    public void testThroughput() {
+        System.out.println("throughput");
+        String result;
+
+        result = HttpUtils.throughput(1000, 1000 * 1000 * 1000);
+        assertEquals("1,000Bytes 1,000.0Bytes/sec", result);
+
+        result = HttpUtils.throughput(1499, 1000 * 1000 * 1000);
+        assertEquals("1,499Bytes 1,499.0Bytes/sec", result);
+
+        result = HttpUtils.throughput(1500, 1000 * 1000 * 1000);
+        assertEquals("1,500Bytes 1.5KBytes/sec", result);
+
+        result = HttpUtils.throughput(1000, 2000 * 1000 * 1000);
+        assertEquals("1,000Bytes 500.0Bytes/sec", result);
+
+        result = HttpUtils.throughput(1499999, 1000 * 1000 * 1000);
+        assertEquals("1,499,999Bytes 1,500.0KBytes/sec", result);
+
+        result = HttpUtils.throughput(1500000, 1000 * 1000 * 1000);
+        assertEquals("1,500,000Bytes 1.5MBytes/sec", result);
+
+        result = HttpUtils.throughput(1, 1000 * 1000 * 1000);
+        assertEquals("1Bytes 1.0Bytes/sec", result);
+
+        result = HttpUtils.throughput(0, 1000 * 1000 * 1000);
+        assertEquals("", result);
+
+        return;
+    }
+
+    /**
+     * Test of formatHttpStat method, of class HttpUtils.
+     */
+    @Test
+    public void testFormatHttpStat() throws Exception{
+        System.out.println("formatHttpStat");
+
+        URL url = new URL("http://example.com");
+        DummyConnection dummy = new DummyConnection(url);
+        long onesec = 1000 * 1000 * 1000;
+        String result;
+        String expected;
+
+        dummy.setRequestMethod("GET");
+        dummy.responseCodeX = 200;
+        dummy.responseMessageX = "OK";
+        result = HttpUtils.formatHttpStat(dummy, 1000, onesec);
+        expected = "GET http://example.com [200 OK]"
+                + " 1,000Bytes 1,000.0Bytes/sec";
+        assertEquals(expected, result);
+
+        return;
+    }
+
+    /**
+     * Test of getUserAgentName method, of class HttpUtils.
+     */
+    @Test
+    public void testGetUserAgentName() {
+        System.out.println("getUserAgentName");
+
+        String result = HttpUtils.getUserAgentName();
+        assertNotNull(result);
+
+        return;
+    }
+
+    /**
+     * Test of getHTMLCharset method, of class HttpUtils.
+     */
+    @Test
+    public void testGetHTMLCharset_URLConnection() throws Exception{
+        System.out.println("getHTMLCharset");
+
+        URL url = new URL("http://example.com");
+        DummyConnection dummy = new DummyConnection(url);
+        String result;
+
+        result = HttpUtils.getHTMLCharset(dummy);
+        assertNull(result);
+
+        dummy.contentTypeX = "text/html;charset=Shift_JIS";
+        result = HttpUtils.getHTMLCharset(dummy);
+        assertEquals("Shift_JIS", result);
+
+        return;
+    }
+
+    /**
+     * Test of getHTMLCharset method, of class HttpUtils.
+     */
+    @Test
+    public void testGetHTMLCharset_String() {
+        System.out.println("getHTMLCharset");
+        String contentType;
+
+        String result;
+
+        contentType = "text/html;charset=Shift_JIS";
+        result = HttpUtils.getHTMLCharset(contentType);
+        assertEquals("Shift_JIS", result);
+
+        contentType = "text/html;charset=\";;;\"";
+        result = HttpUtils.getHTMLCharset(contentType);
+        assertEquals("\";;;\"", result);
+
+        contentType = "text/html ; charset = Shift_JIS ; a = b  ; d=\"xyz\"  ";
+        result = HttpUtils.getHTMLCharset(contentType);
+        assertEquals("Shift_JIS", result);
+
+        contentType = " text/html ;charset=Shift_JIS";
+        result = HttpUtils.getHTMLCharset(contentType);
+        assertEquals("Shift_JIS", result);
+
+        contentType = "tex/html;charset=Shift_JIS";
+        result = HttpUtils.getHTMLCharset(contentType);
+        assertNull(result);
+
+        contentType = "text/htm;charset=Shift_JIS";
+        result = HttpUtils.getHTMLCharset(contentType);
+        assertNull(result);
+
+        contentType = "text / html;charset=Shift_JIS";
+        result = HttpUtils.getHTMLCharset(contentType);
+        assertNull(result);
+
+        contentType = "text/html";
+        result = HttpUtils.getHTMLCharset(contentType);
+        assertNull(result);
+
+        contentType = "text/html;";
+        result = HttpUtils.getHTMLCharset(contentType);
+        assertNull(result);
+
+        contentType = "text/html;charse=Shift_JIS";
+        result = HttpUtils.getHTMLCharset(contentType);
+        assertNull(result);
+
+        return;
+    }
+
+    private class DummyConnection extends HttpURLConnection{
+
+        public int responseCodeX;
+        public String responseMessageX;
+        public String contentTypeX;
+
+        public DummyConnection(URL u) {
+            super(u);
+        }
+
+        @Override
+        public int getResponseCode(){
+            return this.responseCodeX;
+        }
+
+        @Override
+        public String getResponseMessage(){
+            return this.responseMessageX;
+        }
+
+        @Override
+        public String getContentType(){
+            return this.contentTypeX;
+        }
+
+        @Override
+        public void connect() throws IOException {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public void disconnect() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public boolean usingProxy() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+    }
+
 }