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