OSDN Git Service

Maven3対応。
[jindolf/Jindolf.git] / src / main / java / jp / sourceforge / jindolf / Jindolf.java
1 /*
2  * Jindolf main class
3  *
4  * License : The MIT License
5  * Copyright(c) 2008 olyutorskii
6  */
7
8 package jp.sourceforge.jindolf;
9
10 import java.awt.Dimension;
11 import java.awt.EventQueue;
12 import java.awt.GraphicsEnvironment;
13 import java.awt.Window;
14 import java.io.BufferedInputStream;
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.io.InputStreamReader;
18 import java.io.LineNumberReader;
19 import java.io.Reader;
20 import java.lang.reflect.InvocationTargetException;
21 import java.net.URL;
22 import java.security.Permission;
23 import java.text.DateFormat;
24 import java.text.NumberFormat;
25 import java.util.Date;
26 import java.util.Properties;
27 import java.util.concurrent.atomic.AtomicBoolean;
28 import java.util.logging.ConsoleHandler;
29 import java.util.logging.Handler;
30 import java.util.logging.Logger;
31 import java.util.logging.LoggingPermission;
32 import javax.swing.ImageIcon;
33 import javax.swing.JFrame;
34 import javax.swing.JLabel;
35 import javax.swing.JOptionPane;
36 import javax.swing.JWindow;
37 import javax.swing.UIManager;
38
39 /**
40  * Jindolf スタートアップクラス。
41  *
42  * コンストラクタは無いよ。
43  * アプリ開始はstaticメソッド{@link #main(String[])}呼び出しから。
44  */
45 public final class Jindolf{
46
47     /** 実行に最低限必要なJREの版数。 */
48     public static final String MINIMUM_JREVER = "1.5";
49
50
51     /** このClass。 */
52     public static final Class<?>        SELF_KLASS;
53     /** このPackage。 */
54     public static final Package         SELF_PACKAGE;
55     /** ランタイムPackage。 */
56     public static final Package         JRE_PACKAGE;
57     /** 実行環境。 */
58     public static final Runtime         RUNTIME;
59     /** セキュリティマネージャ。 */
60     public static final SecurityManager SEC_MANAGER;
61     /** クラスローダ。 */
62     public static final ClassLoader     LOADER;
63
64
65     /** クラスロード時のナノカウント。 */
66     public static final long NANOCT_LOADED;
67     /** クラスロード時刻(エポックmsec)。 */
68     public static final long EPOCHMS_LOADED;
69
70
71     /** タイトル。 */
72     public static final String TITLE;
73     /** バージョン。 */
74     public static final String VERSION;
75     /** 作者名。 */
76     public static final String AUTHOR;
77     /** 著作権表記。 */
78     public static final String COPYRIGHT;
79     /** ライセンス表記。 */
80     public static final String LICENSE;
81     /** 連絡先。 */
82     public static final String CONTACT;
83     /** 初出。 */
84     public static final String DEBUT;
85     /** その他、何でも書きたいこと。 */
86     public static final String COMMENT;
87     /** クレジット。 */
88     public static final String ID;
89
90     /** 共通ロガー。 */
91     private static final LogWrapper COMMON_LOGGER;
92
93     /** 多重起動防止用セマフォ。 */
94     private static final AtomicBoolean INVOKE_FLAG;
95
96     /** スプラッシュロゴ。 */
97     private static final String RES_LOGOICON =
98             "resources/image/logo.png";
99
100     private static OptionInfo option;
101     private static AppSetting setting;
102
103     /** バージョン定義リソース。 */
104     private static final String RES_VERDEF = "resources/version.properties";
105
106     static{
107         SELF_KLASS   = Jindolf.class;
108         SELF_PACKAGE = SELF_KLASS.getPackage();
109         JRE_PACKAGE  = java.lang.Object.class.getPackage();
110         RUNTIME      = Runtime.getRuntime();
111         SEC_MANAGER  = System.getSecurityManager();
112
113         ClassLoader thisLoader;
114         try{
115             thisLoader = SELF_KLASS.getClassLoader();
116         }catch(SecurityException e){
117             thisLoader = null;
118         }
119         LOADER = thisLoader;
120
121         if( ! JRE_PACKAGE.isCompatibleWith(MINIMUM_JREVER) ){
122             String jreInstalled;
123             try{
124                 jreInstalled = System.getProperty("java.home");
125             }catch(SecurityException e){
126                 jreInstalled = "※インストール位置不明";
127             }
128             String errmsg =
129                     "今このプログラム " + SELF_KLASS.getName() + " は\n"
130                     +"[ " + jreInstalled
131                     +" ]\nにインストールされた"
132                     +" JRE" + JRE_PACKAGE.getSpecificationVersion()
133                     +" の実行環境で実行されようとしました。\n"
134                     +"しかしこのプログラムの実行には"
135                     +" JRE" + MINIMUM_JREVER
136                     + " 以降の実行環境が必要です。\n"
137                     +"おそらく http://www.java.com/ などからの"
138                     +"入手が可能でしょう。";
139
140             errorDialog("実行系の不備", errmsg);
141
142             RUNTIME.exit(1);
143         }
144
145         // ここからJRE1.5解禁。
146
147         NANOCT_LOADED  = System.nanoTime();
148         EPOCHMS_LOADED = System.currentTimeMillis();
149
150         Properties verProp = loadVersionDefinition(SELF_KLASS);
151         TITLE   = getPackageInfo(verProp, "pkg-title.",   "Unknown");
152         VERSION = getPackageInfo(verProp, "pkg-version.", "0");
153         AUTHOR  = getPackageInfo(verProp, "pkg-author.",  "nobody");
154         LICENSE = getPackageInfo(verProp, "pkg-license.", "Unknown");
155         CONTACT = getPackageInfo(verProp, "pkg-contact.", "Unknown");
156         DEBUT   = getPackageInfo(verProp, "pkg-debut.",   "2008");
157         COMMENT = getPackageInfo(verProp, "pkg-comment.", "");
158         COPYRIGHT = "Copyright(c)" +"\u0020"+ DEBUT +"\u0020"+ AUTHOR;
159         ID = TITLE
160             +"\u0020"+ "Ver." + VERSION
161             +"\u0020"+ COPYRIGHT
162             +"\u0020"+ "("+ LICENSE +")";
163
164         Logger jre14Logger = Logger.getLogger(SELF_PACKAGE.getName());
165         COMMON_LOGGER = new LogWrapper(jre14Logger);
166
167         INVOKE_FLAG = new AtomicBoolean(false);
168
169         new Jindolf();
170     }
171
172
173     /**
174      * 隠れコンストラクタ。
175      */
176     private Jindolf(){
177         super();
178         assert this.getClass() == SELF_KLASS;
179         return;
180     }
181
182
183     /**
184      * 起動オプション情報を返す。
185      * @return 起動オプション情報
186      */
187     public static OptionInfo getOptionInfo(){
188         return option;
189     }
190
191     /**
192      * アプリ設定を返す。
193      * @return アプリ設定
194      */
195     public static AppSetting getAppSetting(){
196         return setting;
197     }
198
199     /**
200      * エラーダイアログをビットマップディスプレイに出現させる。
201      * メインウィンドウが整備されるまでの間だけ一時的に使う。
202      * 努力目標:なるべく昔のJRE環境でも例外無く動くように。
203      * @param title タイトル
204      * @param message メッセージ
205      */
206     private static void errorDialog(String title, String message){
207         System.err.println(message);
208         System.err.flush();
209
210         if( ! JRE_PACKAGE.isCompatibleWith("1.2") ){
211             return;
212         }
213
214         if(   JRE_PACKAGE.isCompatibleWith("1.4")
215            && GraphicsEnvironment.isHeadless()){
216             return;
217         }
218
219         JOptionPane.showMessageDialog(null,
220                                       message,
221                                       title,
222                                       JOptionPane.ERROR_MESSAGE);
223
224         return;
225     }
226
227     /**
228      * リソース上のパッケージ定義プロパティをロードする。
229      * MANIFEST.MFが参照できない実行環境での代替品。
230      * @param klass パッケージを構成する任意のクラス
231      * @return プロパティ
232      */
233     private static Properties loadVersionDefinition(Class klass){
234         Properties result = new Properties();
235
236         InputStream istream = klass.getResourceAsStream(RES_VERDEF);
237         try{
238             result.load(istream);
239         }catch(IOException e){
240             return result;
241         }finally{
242             try{
243                 istream.close();
244             }catch(IOException e){
245                 return result;
246             }
247         }
248
249         return result;
250     }
251
252     /**
253      * リソース上のプロパティから
254      * このクラスのパッケージのパッケージ情報を取得する。
255      * MANIFEST.MFが参照できない実行環境での代替品。
256      * @param prop プロパティ
257      * @param prefix 接頭辞
258      * @param defValue 見つからなかった場合のデフォルト値
259      * @return パッケージ情報
260      */
261     public static String getPackageInfo(Properties prop,
262                                           String prefix,
263                                           String defValue){
264         return getPackageInfo(prop, SELF_PACKAGE, prefix, defValue);
265     }
266
267     /**
268      * リソース上のプロパティからパッケージ情報を取得する。
269      * MANIFEST.MFが参照できない実行環境での代替品。
270      * @param prop プロパティ
271      * @param pkg 任意のパッケージ
272      * @param prefix 接頭辞
273      * @param defValue デフォルト値
274      * @return 見つからなかった場合のパッケージ情報
275      */
276     public static String getPackageInfo(Properties prop,
277                                           Package pkg,
278                                           String prefix,
279                                           String defValue){
280         String propName = prefix + pkg.getName();
281         String result = prop.getProperty(propName, defValue);
282         return result;
283     }
284
285     /**
286      * Jindolf の実行が可能なGUI環境でなければ、即時VM実行を終了する。
287      */
288     private static void checkGUIEnvironment(){
289         if(GraphicsEnvironment.isHeadless()){
290             System.err.println(
291                     TITLE
292                     + " はGUI環境と接続できませんでした");
293
294             String dispEnv;
295             try{
296                 dispEnv = System.getenv("DISPLAY");
297             }catch(SecurityException e){
298                 dispEnv = null;
299             }
300
301             // for X11 user
302             if(dispEnv != null){
303                 System.err.println("環境変数 DISPLAY : " + dispEnv);
304             }
305
306             RUNTIME.exit(1);
307         }
308
309         return;
310     }
311
312     /**
313      * コンパイル時のエラーを判定する。
314      * ※ 非Unicode系の開発環境を使いたい人は適当に無視してね。
315      */
316     private static void checkCompileError(){
317         String errmsg =
318                 "ソースコードの文字コードが"
319                +"正しくコンパイルされていないかも。\n"
320                +"あなたは今、オリジナル開発元の意図しない文字コード環境で"
321                +"コンパイルされたプログラムを起動しようとしているよ。\n"
322                +"ソースコードの入手に際して"
323                +"どのような文字コード変換が行われたか認識しているかな?\n"
324                +"コンパイルオプションで正しい文字コードを指定したかな?";
325
326         if(   '狼' != 0x72fc
327            || ' ' != 0x3000
328            || '~'  != 0x007e
329            || '\\' != 0x005c  // バックスラッシュ
330            || '¥'  != 0x00a5  // 半角円通貨
331            || '~' != 0xff5e
332            || '�' != 0xfffd  // Unicode専用特殊文字
333            ){
334             JOptionPane.showMessageDialog(null,
335                                           errmsg,
336                                           "コンパイルの不備",
337                                           JOptionPane.ERROR_MESSAGE);
338             RUNTIME.exit(1);
339         }
340         return;
341     }
342
343     /**
344      * MANIFEST.MFパッケージ定義エラーの検出。
345      * ビルド前にMANIFEST自動生成Antタスク「manifest」を忘れてないかい?
346      */
347     private static void checkPackageDefinition(){
348         String implTitle   = SELF_PACKAGE.getImplementationTitle();
349         String implVersion = SELF_PACKAGE.getImplementationVersion();
350         String implVendor  = SELF_PACKAGE.getImplementationVendor();
351
352         String errmsg = null;
353
354         if(   implTitle != null
355            && ! implTitle.equals(TITLE) ){
356             errmsg = "パッケージ定義とタイトルが一致しません。"
357                     +"["+ implTitle +"]≠["+ TITLE +"]";
358         }else if(   implVersion != null
359                  && ! implVersion.equals(VERSION) ){
360             errmsg = "パッケージ定義とバージョン番号が一致しません。"
361                     +"["+ implVersion +"]≠["+ VERSION +"]";
362         }else if(   implVendor != null
363                  && ! implVendor.equals(AUTHOR) ){
364             errmsg = "パッケージ定義とベンダが一致しません。"
365                     +"["+ implVendor +"]≠["+ AUTHOR +"]";
366         }
367
368         if(errmsg != null){
369             JOptionPane.showMessageDialog(null,
370                                           errmsg,
371                                           "ビルドエラー",
372                                           JOptionPane.ERROR_MESSAGE);
373             RUNTIME.exit(1);
374         }
375
376         return;
377     }
378
379     /**
380      * 標準出力端末にヘルプメッセージ(オプションの説明)を表示する。
381      */
382     private static void showHelpMessage(){
383         System.out.flush();
384         System.err.flush();
385
386         CharSequence helpText = CmdOption.getHelpText();
387         System.out.print(helpText);
388
389         System.out.flush();
390         System.err.flush();
391
392         return;
393     }
394
395     /**
396      * スプラッシュウィンドウを作成する。
397      * JRE1.6以降では呼ばれないはず。
398      * @return 未表示のスプラッシュウィンドウ。
399      */
400     private static Window createSplashWindow(){
401         Window splashWindow = new JWindow();
402
403         URL url = getResource(RES_LOGOICON);
404         ImageIcon logo = new ImageIcon(url);
405         JLabel splashLabel = new JLabel(logo);
406
407         splashWindow.add(splashLabel);
408         splashWindow.pack();
409         splashWindow.setLocationRelativeTo(null); // locate center
410
411         return splashWindow;
412     }
413
414     /**
415      * ロギング初期化。
416      * @param useConsoleLog trueならConsoleHandlerを使う。
417      */
418     private static void initLogging(boolean useConsoleLog){
419         boolean hasPermission = hasLoggingPermission();
420
421         if( ! hasPermission){
422             System.out.println(
423                       "セキュリティ設定により、"
424                     + "ログ設定を変更できませんでした" );
425         }
426
427         Logger jre14Logger = COMMON_LOGGER.getJre14Logger();
428
429         if(hasPermission){
430             jre14Logger.setUseParentHandlers(false);
431             Handler pileHandler = new PileHandler();
432             jre14Logger.addHandler(pileHandler);
433         }
434
435         if(hasPermission && useConsoleLog){
436             Handler consoleHandler = new ConsoleHandler();
437             jre14Logger.addHandler(consoleHandler);
438         }
439
440         return;
441     }
442
443     /**
444      * ログ操作のアクセス権があるか否か判定する。
445      * @return アクセス権があればtrue
446      */
447     public static boolean hasLoggingPermission(){
448         if(SEC_MANAGER == null) return true;
449
450         Permission logPermission = new LoggingPermission("control", null);
451         try{
452             SEC_MANAGER.checkPermission(logPermission);
453         }catch(SecurityException e){
454             return false;
455         }
456
457         return true;
458     }
459
460     /**
461      * 起動時の諸々の情報をログ出力する。
462      */
463     private static void dumpBootInfo(){
464         DateFormat dform = DateFormat.getDateTimeInstance();
465         NumberFormat nform = NumberFormat.getNumberInstance();
466
467         logger().info(
468                 ID + " は "
469                 + dform.format(new Date(EPOCHMS_LOADED))
470                 + " にVM上のクラス "
471                 + SELF_KLASS.getName() + " としてロードされました。 " );
472
473         logger().info("Initial Nano-Count : " + nform.format(NANOCT_LOADED));
474
475         logger().info(
476                 "Max-heap : "
477                 + nform.format(RUNTIME.maxMemory()) + " Byte"
478                 + "   Total-heap : "
479                 + nform.format(RUNTIME.totalMemory()) + " Byte");
480
481         logger().info("\n" + EnvInfo.getVMInfo());
482
483         if(getAppSetting().useConfigPath()){
484             logger().info("設定格納ディレクトリに[ "
485                     + getAppSetting().getConfigPath().getPath()
486                     + " ]が指定されました。");
487         }else{
488             logger().info("設定格納ディレクトリは使いません。");
489         }
490
491         if(   JRE_PACKAGE.isCompatibleWith("1.6")
492            && option.hasOption(CmdOption.OPT_NOSPLASH) ){
493             logger().warn(
494                       "JRE1.6以降では、"
495                     +"Jindolfの-nosplashオプションは無効です。"
496                     + "Java実行系の方でスプラッシュ画面の非表示を"
497                     + "指示してください(おそらく空の-splash:オプション)" );
498         }
499
500         if(LOADER == null){
501             logger().warn(
502                     "セキュリティ設定により、"
503                     +"クラスローダを取得できませんでした");
504         }
505
506         return;
507     }
508
509     /**
510      * 任意のクラス群に対して一括ロード/初期化を単一スレッドで順に行う。
511      * どーしてもクラス初期化の順序に依存する障害が発生する場合や
512      * クラス初期化のオーバーヘッドでGUIの操作性が損なわれるときなどにどうぞ。
513      *
514      * @throws java.lang.LinkageError クラス間リンケージエラー。
515      * @throws java.lang.ExceptionInInitializerError クラス初期化で異常
516      */
517     private static void preInitClass()
518             throws LinkageError,
519                    ExceptionInInitializerError {
520         Object[] classes = {            // Class型 または String型
521             "java.lang.Object",
522             TabBrowser.class,
523             Discussion.class,
524             GlyphDraw.class,
525             java.net.HttpURLConnection.class,
526             java.text.SimpleDateFormat.class,
527             Void.class,
528         };
529
530         for(Object obj : classes){
531             String className;
532             if(obj instanceof Class){
533                 className = ((Class<?>)obj).getName();
534             }else if(obj instanceof String){
535                 className = obj.toString();
536             }else{
537                 continue;
538             }
539
540             try{
541                 if(LOADER != null){
542                     Class.forName(className, true, LOADER);
543                 }else{
544                     Class.forName(className);
545                 }
546             }catch(ClassNotFoundException e){
547                 logger().warn("クラスの明示的ロードに失敗しました", e);
548                 continue;
549             }
550         }
551
552         return;
553     }
554
555     /**
556      * AWTイベントディスパッチスレッド版スタートアップエントリ。
557      */
558     private static void startGUI(){
559         LandsModel model = new LandsModel();
560         model.loadLandList();
561
562         JFrame topFrame = buildMVC(model);
563
564         GUIUtils.modifyWindowAttributes(topFrame, true, false, true);
565
566         topFrame.pack();
567
568         Dimension initGeometry =
569                 new Dimension(setting.initialFrameWidth(),
570                               setting.initialFrameHeight());
571         topFrame.setSize(initGeometry);
572
573         if(   setting.initialFrameXpos() <= Integer.MIN_VALUE
574            || setting.initialFrameYpos() <= Integer.MIN_VALUE ){
575             topFrame.setLocationByPlatform(true);
576         }else{
577             topFrame.setLocation(setting.initialFrameXpos(),
578                                  setting.initialFrameYpos() );
579         }
580
581         topFrame.setVisible(true);
582
583         return;
584     }
585
586     /**
587      * モデル・ビュー・コントローラの結合。
588      *
589      * @param model 最上位のデータモデル
590      * @return アプリケーションのトップフレーム
591      */
592     private static JFrame buildMVC(LandsModel model){
593         ActionManager actionManager = new ActionManager();
594         TopView topView = new TopView();
595
596         Controller controller = new Controller(actionManager, topView, model);
597
598         JFrame topFrame = controller.createTopFrame();
599
600         return topFrame;
601     }
602
603     /**
604      * リソースからUTF-8で記述されたテキストデータをロードする。
605      * @param resourceName リソース名
606      * @return テキスト文字列
607      * @throws java.io.IOException 入出力の異常。おそらくビルドミス。
608      */
609     public static CharSequence loadResourceText(String resourceName)
610             throws IOException{
611         InputStream is;
612         is = getResourceAsStream(resourceName);
613         is = new BufferedInputStream(is);
614         Reader reader = new InputStreamReader(is, "UTF-8");
615         LineNumberReader lineReader = new LineNumberReader(reader);
616
617         StringBuilder result = new StringBuilder();
618         try{
619             for(;;){
620                 String line = lineReader.readLine();
621                 if(line == null) break;
622                 if(line.startsWith("#")) continue;
623                 result.append(line).append('\n');
624             }
625         }finally{
626             lineReader.close();
627         }
628
629         return result;
630     }
631
632     /**
633      * クラスローダを介してリソースからの入力を生成する。
634      * @param name リソース名
635      * @return リソースからの入力
636      */
637     public static InputStream getResourceAsStream(String name){
638         return SELF_KLASS.getResourceAsStream(name);
639     }
640
641     /**
642      * クラスローダを介してリソース読み込み用URLを生成する。
643      * @param name リソース名
644      * @return URL
645      */
646     public static URL getResource(String name){
647         return SELF_KLASS.getResource(name);
648     }
649
650     /**
651      * 共通ロガーを取得する。
652      * @return 共通ロガー
653      */
654     public static LogWrapper logger(){
655         return COMMON_LOGGER;
656     }
657
658     /**
659      * VMごとプログラムを終了する。
660      * ※おそらく随所でシャットダウンフックが起動されるはず。
661      *
662      * @param exitCode 終了コード
663      * @throws java.lang.SecurityException セキュリティ違反
664      */
665     public static void exit(int exitCode) throws SecurityException{
666         logger().info(
667                 "終了コード["
668                 + exitCode
669                 + "]でVMごとアプリケーションを終了します。" );
670         RUNTIME.runFinalization();
671         System.out.flush();
672         System.err.flush();
673         try{
674             RUNTIME.exit(exitCode);
675         }catch(SecurityException e){
676             logger().warn(
677                      "セキュリティ設定により、"
678                     +"VMを終了させることができません。", e);
679             throw e;
680         }
681         return;
682     }
683
684     /**
685      * Jindolf のスタートアップエントリ。
686      *
687      * @param args コマンドライン引数
688      */
689     public static void main(final String[] args){
690         // VM内二重起動チェック
691         boolean hasInvoked = ! INVOKE_FLAG.compareAndSet(false, true);
692         if(hasInvoked){
693             String errmsg = "二度目以降の起動がキャンセルされました。";
694             errorDialog("多重起動", errmsg);
695
696             // exitせずに戻るのみ
697             return;
698         }
699
700         checkGUIEnvironment();
701
702         // ここからGUIウィンドウとマウス解禁
703
704         checkCompileError();
705         checkPackageDefinition();
706
707         try{
708             option = OptionInfo.parseOptions(args);
709         }catch(IllegalArgumentException e){
710             String message = e.getLocalizedMessage();
711             System.err.println(message);
712             System.err.println(
713                 "起動オプション一覧は、"
714                 + "起動オプションに「"
715                 + CmdOption.OPT_HELP.toHyphened()
716                 + "」を指定すると確認できます。" );
717             Jindolf.RUNTIME.exit(1);
718             assert false;
719             return;
720         }
721
722         if(option.hasOption(CmdOption.OPT_HELP)){
723             showHelpMessage();
724             RUNTIME.exit(0);
725             return;
726         }
727
728         if(option.hasOption(CmdOption.OPT_VERSION)){
729             System.out.println(ID);
730             RUNTIME.exit(0);
731             return;
732         }
733
734         // あらゆるSwingコンポーネント操作より前に必要。
735         if(option.hasOption(CmdOption.OPT_BOLDMETAL)){
736             // もの凄く日本語表示が汚くなるかもよ!注意
737             UIManager.put("swing.boldMetal", Boolean.TRUE);
738         }else{
739             UIManager.put("swing.boldMetal", Boolean.FALSE);
740         }
741
742         // JRE1.5用スプラッシュウィンドウ
743         Window splashWindow = null;
744         if(   ! JRE_PACKAGE.isCompatibleWith("1.6")
745            && ! option.hasOption(CmdOption.OPT_NOSPLASH) ){
746             splashWindow = createSplashWindow();
747             splashWindow.setVisible(true);
748             Thread.yield();
749         }
750
751         setting = new AppSetting();
752         setting.applyOptionInfo(option);
753
754         if(option.hasOption(CmdOption.OPT_VMINFO)){
755             System.out.println(EnvInfo.getVMInfo());
756         }
757
758         initLogging(option.hasOption(CmdOption.OPT_CONSOLELOG));
759         // ここからロギング解禁
760         // Jindolf.exit()もここから解禁
761
762         dumpBootInfo();
763
764         ConfigFile.setupConfigDirectory();
765         ConfigFile.setupLockFile();
766         // ここから設定格納ディレクトリ解禁
767
768         setting.loadConfig();
769
770         RUNTIME.addShutdownHook(new Thread(){
771             @Override
772             public void run(){
773                 logger().info("シャットダウン処理に入ります…");
774                 System.out.flush();
775                 System.err.flush();
776                 RUNTIME.gc();
777                 Thread.yield();
778                 RUNTIME.runFinalization(); // 危険?
779                 Thread.yield();
780                 return;
781             }
782         });
783
784         preInitClass();
785
786         GUIUtils.replaceEventQueue();
787
788         boolean hasError = false;
789         try{
790             EventQueue.invokeAndWait(new Runnable(){
791                 public void run(){
792                     startGUI();
793                     return;
794                 }
795             });
796         }catch(InvocationTargetException e){
797             logger().fatal("アプリケーション初期化に失敗しました", e);
798             e.printStackTrace(System.err);
799             hasError = true;
800         }catch(InterruptedException e){
801             logger().fatal("アプリケーション初期化に失敗しました", e);
802             e.printStackTrace(System.err);
803             hasError = true;
804         }finally{
805             if(splashWindow != null){
806                 splashWindow.setVisible(false);
807                 splashWindow.dispose();
808                 splashWindow = null;
809             }
810         }
811
812         if(hasError) exit(1);
813
814         return;
815     }
816
817 }