OSDN Git Service

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