OSDN Git Service

6589322b903a76233deaad06d60bf246cad84e15
[jindolf/Jindolf.git] / src / main / java / jp / sfjp / jindolf / JindolfMain.java
1 /*
2  * Jindolf main class
3  *
4  * License : The MIT License
5  * Copyright(c) 2008 olyutorskii
6  */
7
8 package jp.sfjp.jindolf;
9
10 import java.awt.Dimension;
11 import java.awt.EventQueue;
12 import java.io.PrintStream;
13 import java.lang.reflect.InvocationTargetException;
14 import java.text.MessageFormat;
15 import java.util.Locale;
16 import java.util.logging.Level;
17 import java.util.logging.Logger;
18 import javax.swing.JFrame;
19 import javax.swing.UIManager;
20 import jp.sfjp.jindolf.config.AppSetting;
21 import jp.sfjp.jindolf.config.CmdOption;
22 import jp.sfjp.jindolf.config.ConfigDirUi;
23 import jp.sfjp.jindolf.config.ConfigStore;
24 import jp.sfjp.jindolf.config.EnvInfo;
25 import jp.sfjp.jindolf.config.FileUtils;
26 import jp.sfjp.jindolf.config.OptionInfo;
27 import jp.sfjp.jindolf.data.LandsTreeModel;
28 import jp.sfjp.jindolf.log.LogUtils;
29 import jp.sfjp.jindolf.log.LoggingDispatcher;
30 import jp.sfjp.jindolf.util.GUIUtils;
31 import jp.sfjp.jindolf.view.ActionManager;
32 import jp.sfjp.jindolf.view.WindowManager;
33
34 /**
35  * Jindolf スタートアップクラス。
36  *
37  * <p>{@link JindolfJre18}の下請けとして本格的なJindolf起動処理に入る。
38  */
39 public final class JindolfMain {
40
41     /** クラスロード時のナノカウント。 */
42     public static final long NANOCT_LOADED;
43     /** クラスロード時刻(エポックmsec)。 */
44     public static final long EPOCHMS_LOADED;
45
46
47     /** ロガー。 */
48     private static final Logger LOGGER = Logger.getAnonymousLogger();
49
50     private static final String LOG_LOADED =
51               "{0} は {1,date} {2,time} に"
52             + "VM上のクラス {3} としてロードされました。 ";
53     private static final String LOG_NANOCT =
54             "Initial Nano-Count : {0}";
55     private static final String LOG_HEAP =
56             "Max-heap : {0} Bytes   Total-heap : {1} Bytes";
57     private static final String LOG_CONF =
58             "設定格納ディレクトリに[ {0} ]が指定されました。";
59     private static final String LOG_NOCONF =
60             "設定格納ディレクトリは使いません。";
61     private static final String FATALMSG_INITFAIL =
62             "アプリケーション初期化に失敗しました";
63     private static final String ERRMSG_HELP =
64               "起動オプション一覧は、"
65             + "起動オプションに「{0}」を指定すると確認できます。";
66
67     private static final PrintStream STDOUT = System.out;
68     private static final PrintStream STDERR = System.err;
69
70     static{
71         NANOCT_LOADED  = System.nanoTime();
72         EPOCHMS_LOADED = System.currentTimeMillis();
73     }
74
75
76     /**
77      * 隠れコンストラクタ。
78      */
79     private JindolfMain(){
80         assert false;
81     }
82
83
84     /**
85      * 標準出力および標準エラー出力をフラッシュする。
86      */
87     private static void flush(){
88         STDOUT.flush();
89         STDERR.flush();
90         return;
91     }
92
93     /**
94      * 標準出力端末にヘルプメッセージ(オプションの説明)を表示する。
95      */
96     private static void showHelpMessage(){
97         flush();
98
99         String helpText = CmdOption.getHelpText();
100         STDOUT.print(helpText);
101
102         flush();
103
104         return;
105     }
106
107     /**
108      * 起動時の諸々の情報をログ出力する。
109      */
110     private static void logBootInfo(){
111         Object[] logArgs;
112
113         logArgs = new Object[]{
114             VerInfo.ID,
115             EPOCHMS_LOADED,
116             EPOCHMS_LOADED,
117             Jindolf.class.getName(),
118         };
119         LOGGER.log(Level.INFO, LOG_LOADED, logArgs);
120
121         LOGGER.log(Level.INFO, LOG_NANOCT, NANOCT_LOADED);
122
123         Runtime runtime = Runtime.getRuntime();
124         logArgs = new Object[]{
125             runtime.maxMemory(),
126             runtime.totalMemory(),
127         };
128         LOGGER.log(Level.INFO, LOG_HEAP, logArgs);
129
130         if(FileUtils.isWindowsOSFs()){
131             LOGGER.info("Windows環境と認識されました。");
132         }
133
134         if(FileUtils.isMacOSXFs()){
135             LOGGER.info("macOS環境と認識されました。");
136         }
137
138         Locale locale = Locale.getDefault();
139         String localeTxt = locale.toString();
140         LOGGER.log(Level.INFO, "ロケールに{0}が用いられます。", localeTxt);
141
142         return;
143     }
144     
145     /**
146      * 起動時の諸々の情報をログ出力する。
147      * 
148      * @param optinfo コマンドラインオプション
149      */
150     private static void logBootInfo(OptionInfo optinfo){
151         StringBuilder bootArgs = new StringBuilder();
152         
153         bootArgs.append("\n\n").append("起動時引数:\n");
154         optinfo.getInvokeArgList().forEach(arg -> 
155             bootArgs.append("\u0020\u0020").append(arg).append('\n')
156         );
157         bootArgs.append('\n');
158         
159         bootArgs.append(EnvInfo.getVMInfo());
160         
161         LOGGER.info(bootArgs.toString());
162
163         return;
164     }
165     
166     /**
167      * 起動時の諸々の情報をログ出力する。
168      * 
169      * @param configStore  設定ディレクトリ情報
170      */
171     private static void logBootInfo(ConfigStore configStore){
172         if(configStore.useStoreFile()){
173             LOGGER.log(Level.INFO, LOG_CONF, configStore.getConfigDir());
174         }else{
175             LOGGER.info(LOG_NOCONF);
176         }
177         return;
178     }
179     
180     /**
181      * JindolfMain のスタートアップエントリ。
182      * @param args コマンドライン引数
183      * @return 起動に成功すれば0。失敗したら0以外。
184      */
185     public static int main(String... args){
186         OptionInfo optinfo;
187         int exitCode;
188
189         try{
190             optinfo = OptionInfo.parseOptions(args);
191         }catch(IllegalArgumentException e){
192             String message = e.getLocalizedMessage();
193             STDERR.println(message);
194
195             String info =
196                     MessageFormat.format(ERRMSG_HELP, CmdOption.OPT_HELP);
197             STDERR.println(info);
198
199             exitCode = 1;
200             return exitCode;
201         }
202
203         exitCode = main(optinfo);
204
205         return exitCode;
206     }
207
208     /**
209      * JindolfMain のスタートアップエントリ。
210      * @param optinfo コマンドライン引数情報
211      * @return 起動に成功すれば0。失敗したら0以外。
212      */
213     public static int main(OptionInfo optinfo){
214         int exitCode;
215
216         if(optinfo.hasOption(CmdOption.OPT_HELP)){
217             showHelpMessage();
218             exitCode = 0;
219             return exitCode;
220         }
221
222         if(optinfo.hasOption(CmdOption.OPT_VERSION)){
223             STDOUT.println(VerInfo.ID);
224             exitCode = 0;
225             return exitCode;
226         }
227
228         // ここ以降、アプリウィンドウの生成と表示に向けてまっしぐら。
229
230         // あらゆるSwing文字列表示処理より前に必要。
231         // システムプロパティ swing.boldMetal は無視される。
232         Boolean boldFlag;
233         if(optinfo.hasOption(CmdOption.OPT_BOLDMETAL)){
234             // もの凄く日本語表示が汚くなるかもよ!注意
235             boldFlag = Boolean.TRUE;
236         }else{
237             boldFlag = Boolean.FALSE;
238         }
239         UIManager.put("swing.boldMetal", boldFlag);
240
241         exitCode = splashedMain(optinfo);
242
243         return exitCode;
244     }
245
246     /**
247      * JindolfMain のスタートアップエントリ。
248      *
249      * <p>JRE1.5までの間、
250      * スプラッシュウィンドウが出ている状態として想定されていた。
251      * 時間のかかる初期化処理はなるべくこの中へ。
252      *
253      * @param optinfo コマンドライン引数情報
254      * @return 起動に成功すれば0。失敗したら0以外。
255      */
256     public static int splashedMain(OptionInfo optinfo){
257         if(optinfo.hasOption(CmdOption.OPT_VMINFO)){
258             STDOUT.println(EnvInfo.getVMInfo());
259         }
260
261         LogUtils.initRootLogger(optinfo.hasOption(CmdOption.OPT_CONSOLELOG));
262         // ここからロギング解禁
263
264         logBootInfo();
265         logBootInfo(optinfo);
266         
267         final AppSetting appSetting = new AppSetting(optinfo);
268         ConfigStore configStore = appSetting.getConfigStore();
269         logBootInfo(configStore);
270         
271         ConfigDirUi.prepareConfigDir(configStore);
272         ConfigDirUi.tryLock(configStore);
273         // ここから設定格納ディレクトリ解禁
274
275         appSetting.loadConfig();
276
277         final Runtime runtime = Runtime.getRuntime();
278         runtime.addShutdownHook(new Thread(){
279             /** {@inheritDoc} */
280             @Override
281             @SuppressWarnings("CallToThreadYield")
282             public void run(){
283                 LOGGER.info("シャットダウン処理に入ります…");
284                 flush();
285                 runtime.gc();
286                 Thread.yield();
287                 runtime.runFinalization(); // 危険?
288                 Thread.yield();
289                 return;
290             }
291         });
292
293         LoggingDispatcher.replaceEventQueue();
294
295         int exitCode = 0;
296         try{
297             EventQueue.invokeAndWait(() -> 
298                 startGUI(appSetting)
299             );
300         }catch(InvocationTargetException | InterruptedException e){
301             LOGGER.log(Level.SEVERE, FATALMSG_INITFAIL, e);
302             e.printStackTrace(STDERR);
303             exitCode = 1;
304         }
305
306         return exitCode;
307     }
308
309     /**
310      * AWTイベントディスパッチスレッド版スタートアップエントリ。
311      * @param appSetting アプリ設定
312      */
313     private static void startGUI(AppSetting appSetting){
314         JFrame topFrame = buildMVC(appSetting);
315
316         GUIUtils.modifyWindowAttributes(topFrame, true, false, true);
317
318         topFrame.pack();
319
320         Dimension initGeometry =
321                 new Dimension(appSetting.initialFrameWidth(),
322                               appSetting.initialFrameHeight());
323         topFrame.setSize(initGeometry);
324
325         if(    appSetting.initialFrameXpos() <= Integer.MIN_VALUE
326             || appSetting.initialFrameYpos() <= Integer.MIN_VALUE ){
327             topFrame.setLocationByPlatform(true);
328         }else{
329             topFrame.setLocation(appSetting.initialFrameXpos(),
330                                  appSetting.initialFrameYpos() );
331         }
332
333         topFrame.setVisible(true);
334
335         return;
336     }
337
338     /**
339      * モデル・ビュー・コントローラの結合。
340      * @param appSetting アプリ設定
341      * @return アプリケーションのトップフレーム
342      */
343     private static JFrame buildMVC(AppSetting appSetting){
344         LandsTreeModel model = new LandsTreeModel();
345         WindowManager windowManager = new WindowManager();
346         ActionManager actionManager = new ActionManager();
347
348         Controller controller = new Controller(model,
349                                                windowManager,
350                                                actionManager,
351                                                appSetting );
352
353         JFrame topFrame = controller.getTopFrame();
354
355         return topFrame;
356     }
357
358 }