4 * License : The MIT License
\r
5 * Copyright(c) 2008 olyutorskii
\r
8 package jp.sourceforge.jindolf;
\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
40 * Jindolf スタートアップクラス。
\r
43 * アプリ開始はstaticメソッド{@link #main(String[])}呼び出しから。
\r
45 public final class Jindolf{
\r
47 /** 実行に最低限必要なJREの版数。 */
\r
48 public static final String MINIMUM_JREVER = "1.5";
\r
52 public static final Class<?> SELF_KLASS;
\r
54 public static final Package SELF_PACKAGE;
\r
55 /** ランタイムPackage。 */
\r
56 public static final Package JRE_PACKAGE;
\r
58 public static final Runtime RUNTIME;
\r
60 public static final SecurityManager SEC_MANAGER;
\r
62 public static final ClassLoader LOADER;
\r
65 /** クラスロード時のナノカウント。 */
\r
66 public static final long NANOCT_LOADED;
\r
67 /** クラスロード時刻(エポックmsec)。 */
\r
68 public static final long EPOCHMS_LOADED;
\r
72 public static final String TITLE;
\r
74 public static final String VERSION;
\r
76 public static final String AUTHOR;
\r
78 public static final String COPYRIGHT;
\r
80 public static final String LICENSE;
\r
82 public static final String CONTACT;
\r
84 public static final String DEBUT;
\r
85 /** その他、何でも書きたいこと。 */
\r
86 public static final String COMMENT;
\r
88 public static final String ID;
\r
91 private static final LogWrapper COMMON_LOGGER;
\r
94 private static final AtomicBoolean INVOKE_FLAG;
\r
97 private static final String RES_LOGOICON =
\r
98 "resources/image/logo.png";
\r
100 private static OptionInfo option;
\r
101 private static AppSetting setting;
\r
103 /** バージョン定義リソース。 */
\r
104 private static final String RES_VERDEF = "resources/version.properties";
\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
113 ClassLoader thisLoader;
\r
115 thisLoader = SELF_KLASS.getClassLoader();
\r
116 }catch(SecurityException e){
\r
119 LOADER = thisLoader;
\r
121 if( ! JRE_PACKAGE.isCompatibleWith(MINIMUM_JREVER) ){
\r
122 String jreInstalled;
\r
124 jreInstalled = System.getProperty("java.home");
\r
125 }catch(SecurityException e){
\r
126 jreInstalled = "※インストール位置不明";
\r
129 "今このプログラム " + SELF_KLASS.getName() + " は\n"
\r
130 +"[ " + jreInstalled
\r
132 +" JRE" + JRE_PACKAGE.getSpecificationVersion()
\r
133 +" の実行環境で実行されようとしました。\n"
\r
135 +" JRE" + MINIMUM_JREVER
\r
136 + " 以降の実行環境が必要です。\n"
\r
137 +"おそらく http://www.java.com/ などからの"
\r
140 errorDialog("実行系の不備", errmsg);
\r
147 NANOCT_LOADED = System.nanoTime();
\r
148 EPOCHMS_LOADED = System.currentTimeMillis();
\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
160 +"\u0020"+ "Ver." + VERSION
\r
161 +"\u0020"+ COPYRIGHT
\r
162 +"\u0020"+ "("+ LICENSE +")";
\r
164 Logger jre14Logger = Logger.getLogger(SELF_PACKAGE.getName());
\r
165 COMMON_LOGGER = new LogWrapper(jre14Logger);
\r
167 INVOKE_FLAG = new AtomicBoolean(false);
\r
178 assert this.getClass() == SELF_KLASS;
\r
185 * @return 起動オプション情報
\r
187 public static OptionInfo getOptionInfo(){
\r
195 public static AppSetting getAppSetting(){
\r
200 * エラーダイアログをビットマップディスプレイに出現させる。
\r
201 * メインウィンドウが整備されるまでの間だけ一時的に使う。
\r
202 * 努力目標:なるべく昔のJRE環境でも例外無く動くように。
\r
203 * @param title タイトル
\r
204 * @param message メッセージ
\r
206 private static void errorDialog(String title, String message){
\r
207 System.err.println(message);
\r
208 System.err.flush();
\r
210 if( ! JRE_PACKAGE.isCompatibleWith("1.2") ){
\r
214 if( JRE_PACKAGE.isCompatibleWith("1.4")
\r
215 && GraphicsEnvironment.isHeadless()){
\r
219 JOptionPane.showMessageDialog(null,
\r
222 JOptionPane.ERROR_MESSAGE);
\r
228 * リソース上のパッケージ定義プロパティをロードする。
\r
229 * MANIFEST.MFが参照できない実行環境での代替品。
\r
230 * @param klass パッケージを構成する任意のクラス
\r
233 private static Properties loadVersionDefinition(Class klass){
\r
234 Properties result = new Properties();
\r
236 InputStream istream = klass.getResourceAsStream(RES_VERDEF);
\r
238 result.load(istream);
\r
239 }catch(IOException e){
\r
244 }catch(IOException e){
\r
254 * このクラスのパッケージのパッケージ情報を取得する。
\r
255 * MANIFEST.MFが参照できない実行環境での代替品。
\r
256 * @param prop プロパティ
\r
257 * @param prefix 接頭辞
\r
258 * @param defValue 見つからなかった場合のデフォルト値
\r
261 public static String getPackageInfo(Properties prop,
\r
264 return getPackageInfo(prop, SELF_PACKAGE, prefix, defValue);
\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
276 public static String getPackageInfo(Properties prop,
\r
280 String propName = prefix + pkg.getName();
\r
281 String result = prop.getProperty(propName, defValue);
\r
286 * Jindolf の実行が可能なGUI環境でなければ、即時VM実行を終了する。
\r
288 private static void checkGUIEnvironment(){
\r
289 if(GraphicsEnvironment.isHeadless()){
\r
290 System.err.println(
\r
292 + " はGUI環境と接続できませんでした");
\r
296 dispEnv = System.getenv("DISPLAY");
\r
297 }catch(SecurityException e){
\r
302 if(dispEnv != null){
\r
303 System.err.println("環境変数 DISPLAY : " + dispEnv);
\r
314 * ※ 非Unicode系の開発環境を使いたい人は適当に無視してね。
\r
316 private static void checkCompileError(){
\r
319 +"正しくコンパイルされていないかも。\n"
\r
320 +"あなたは今、オリジナル開発元の意図しない文字コード環境で"
\r
321 +"コンパイルされたプログラムを起動しようとしているよ。\n"
\r
323 +"どのような文字コード変換が行われたか認識しているかな?\n"
\r
324 +"コンパイルオプションで正しい文字コードを指定したかな?";
\r
329 || '\\' != 0x005c // バックスラッシュ
\r
330 || '¥' != 0x00a5 // 半角円通貨
\r
332 || '�' != 0xfffd // Unicode専用特殊文字
\r
334 JOptionPane.showMessageDialog(null,
\r
337 JOptionPane.ERROR_MESSAGE);
\r
344 * MANIFEST.MFパッケージ定義エラーの検出。
\r
345 * ビルド前にMANIFEST自動生成Antタスク「manifest」を忘れてないかい?
\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
352 String errmsg = null;
\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
368 if(errmsg != null){
\r
369 JOptionPane.showMessageDialog(null,
\r
372 JOptionPane.ERROR_MESSAGE);
\r
380 * 標準出力端末にヘルプメッセージ(オプションの説明)を表示する。
\r
382 private static void showHelpMessage(){
\r
383 System.out.flush();
\r
384 System.err.flush();
\r
386 CharSequence helpText = CmdOption.getHelpText();
\r
387 System.out.print(helpText);
\r
389 System.out.flush();
\r
390 System.err.flush();
\r
396 * スプラッシュウィンドウを作成する。
\r
397 * JRE1.6以降では呼ばれないはず。
\r
398 * @return 未表示のスプラッシュウィンドウ。
\r
400 private static Window createSplashWindow(){
\r
401 Window splashWindow = new JWindow();
\r
403 URL url = getResource(RES_LOGOICON);
\r
404 ImageIcon logo = new ImageIcon(url);
\r
405 JLabel splashLabel = new JLabel(logo);
\r
407 splashWindow.add(splashLabel);
\r
408 splashWindow.pack();
\r
409 splashWindow.setLocationRelativeTo(null); // locate center
\r
411 return splashWindow;
\r
416 * @param useConsoleLog trueならConsoleHandlerを使う。
\r
418 private static void initLogging(boolean useConsoleLog){
\r
419 boolean hasPermission = hasLoggingPermission();
\r
421 if( ! hasPermission){
\r
422 System.out.println(
\r
424 + "ログ設定を変更できませんでした" );
\r
427 Logger jre14Logger = COMMON_LOGGER.getJre14Logger();
\r
430 jre14Logger.setUseParentHandlers(false);
\r
431 Handler pileHandler = new PileHandler();
\r
432 jre14Logger.addHandler(pileHandler);
\r
435 if(hasPermission && useConsoleLog){
\r
436 Handler consoleHandler = new ConsoleHandler();
\r
437 jre14Logger.addHandler(consoleHandler);
\r
444 * ログ操作のアクセス権があるか否か判定する。
\r
445 * @return アクセス権があればtrue
\r
447 public static boolean hasLoggingPermission(){
\r
448 if(SEC_MANAGER == null) return true;
\r
450 Permission logPermission = new LoggingPermission("control", null);
\r
452 SEC_MANAGER.checkPermission(logPermission);
\r
453 }catch(SecurityException e){
\r
461 * 起動時の諸々の情報をログ出力する。
\r
463 private static void dumpBootInfo(){
\r
464 DateFormat dform = DateFormat.getDateTimeInstance();
\r
465 NumberFormat nform = NumberFormat.getNumberInstance();
\r
469 + dform.format(new Date(EPOCHMS_LOADED))
\r
471 + SELF_KLASS.getName() + " としてロードされました。 " );
\r
473 logger().info("Initial Nano-Count : " + nform.format(NANOCT_LOADED));
\r
477 + nform.format(RUNTIME.maxMemory()) + " Byte"
\r
479 + nform.format(RUNTIME.totalMemory()) + " Byte");
\r
481 logger().info("\n" + EnvInfo.getVMInfo());
\r
483 if(getAppSetting().useConfigPath()){
\r
484 logger().info("設定格納ディレクトリに[ "
\r
485 + getAppSetting().getConfigPath().getPath()
\r
488 logger().info("設定格納ディレクトリは使いません。");
\r
491 if( JRE_PACKAGE.isCompatibleWith("1.6")
\r
492 && option.hasOption(CmdOption.OPT_NOSPLASH) ){
\r
495 +"Jindolfの-nosplashオプションは無効です。"
\r
496 + "Java実行系の方でスプラッシュ画面の非表示を"
\r
497 + "指示してください(おそらく空の-splash:オプション)" );
\r
500 if(LOADER == null){
\r
503 +"クラスローダを取得できませんでした");
\r
510 * 任意のクラス群に対して一括ロード/初期化を単一スレッドで順に行う。
\r
511 * どーしてもクラス初期化の順序に依存する障害が発生する場合や
\r
512 * クラス初期化のオーバーヘッドでGUIの操作性が損なわれるときなどにどうぞ。
\r
514 * @throws java.lang.LinkageError クラス間リンケージエラー。
\r
515 * @throws java.lang.ExceptionInInitializerError クラス初期化で異常
\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
525 java.net.HttpURLConnection.class,
\r
526 java.text.SimpleDateFormat.class,
\r
530 for(Object obj : classes){
\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
541 if(LOADER != null){
\r
542 Class.forName(className, true, LOADER);
\r
544 Class.forName(className);
\r
546 }catch(ClassNotFoundException e){
\r
547 logger().warn("クラスの明示的ロードに失敗しました", e);
\r
556 * AWTイベントディスパッチスレッド版スタートアップエントリ。
\r
558 private static void startGUI(){
\r
559 LandsModel model = new LandsModel();
\r
560 model.loadLandList();
\r
562 JFrame topFrame = buildMVC(model);
\r
564 GUIUtils.modifyWindowAttributes(topFrame, true, false, true);
\r
568 Dimension initGeometry =
\r
569 new Dimension(setting.initialFrameWidth(),
\r
570 setting.initialFrameHeight());
\r
571 topFrame.setSize(initGeometry);
\r
573 if( setting.initialFrameXpos() <= Integer.MIN_VALUE
\r
574 || setting.initialFrameYpos() <= Integer.MIN_VALUE ){
\r
575 topFrame.setLocationByPlatform(true);
\r
577 topFrame.setLocation(setting.initialFrameXpos(),
\r
578 setting.initialFrameYpos() );
\r
581 topFrame.setVisible(true);
\r
587 * モデル・ビュー・コントローラの結合。
\r
589 * @param model 最上位のデータモデル
\r
590 * @return アプリケーションのトップフレーム
\r
592 private static JFrame buildMVC(LandsModel model){
\r
593 ActionManager actionManager = new ActionManager();
\r
594 TopView topView = new TopView();
\r
596 Controller controller = new Controller(actionManager, topView, model);
\r
598 JFrame topFrame = controller.createTopFrame();
\r
604 * リソースからUTF-8で記述されたテキストデータをロードする。
\r
605 * @param resourceName リソース名
\r
607 * @throws java.io.IOException 入出力の異常。おそらくビルドミス。
\r
609 public static CharSequence loadResourceText(String resourceName)
\r
610 throws IOException{
\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
617 StringBuilder result = new StringBuilder();
\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
626 lineReader.close();
\r
633 * クラスローダを介してリソースからの入力を生成する。
\r
634 * @param name リソース名
\r
635 * @return リソースからの入力
\r
637 public static InputStream getResourceAsStream(String name){
\r
638 return SELF_KLASS.getResourceAsStream(name);
\r
642 * クラスローダを介してリソース読み込み用URLを生成する。
\r
643 * @param name リソース名
\r
646 public static URL getResource(String name){
\r
647 return SELF_KLASS.getResource(name);
\r
654 public static LogWrapper logger(){
\r
655 return COMMON_LOGGER;
\r
660 * ※おそらく随所でシャットダウンフックが起動されるはず。
\r
662 * @param exitCode 終了コード
\r
663 * @throws java.lang.SecurityException セキュリティ違反
\r
665 public static void exit(int exitCode) throws SecurityException{
\r
669 + "]でVMごとアプリケーションを終了します。" );
\r
670 RUNTIME.runFinalization();
\r
671 System.out.flush();
\r
672 System.err.flush();
\r
674 RUNTIME.exit(exitCode);
\r
675 }catch(SecurityException e){
\r
678 +"VMを終了させることができません。", e);
\r
685 * Jindolf のスタートアップエントリ。
\r
687 * @param args コマンドライン引数
\r
689 public static void main(final String[] args){
\r
691 boolean hasInvoked = ! INVOKE_FLAG.compareAndSet(false, true);
\r
693 String errmsg = "二度目以降の起動がキャンセルされました。";
\r
694 errorDialog("多重起動", errmsg);
\r
700 checkGUIEnvironment();
\r
702 // ここからGUIウィンドウとマウス解禁
\r
704 checkCompileError();
\r
705 checkPackageDefinition();
\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
715 + CmdOption.OPT_HELP.toHyphened()
\r
716 + "」を指定すると確認できます。" );
\r
717 Jindolf.RUNTIME.exit(1);
\r
722 if(option.hasOption(CmdOption.OPT_HELP)){
\r
728 if(option.hasOption(CmdOption.OPT_VERSION)){
\r
729 System.out.println(ID);
\r
734 // あらゆるSwingコンポーネント操作より前に必要。
\r
735 if(option.hasOption(CmdOption.OPT_BOLDMETAL)){
\r
736 // もの凄く日本語表示が汚くなるかもよ!注意
\r
737 UIManager.put("swing.boldMetal", Boolean.TRUE);
\r
739 UIManager.put("swing.boldMetal", Boolean.FALSE);
\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
751 setting = new AppSetting();
\r
752 setting.applyOptionInfo(option);
\r
754 if(option.hasOption(CmdOption.OPT_VMINFO)){
\r
755 System.out.println(EnvInfo.getVMInfo());
\r
758 initLogging(option.hasOption(CmdOption.OPT_CONSOLELOG));
\r
760 // Jindolf.exit()もここから解禁
\r
764 ConfigFile.setupConfigDirectory();
\r
765 ConfigFile.setupLockFile();
\r
766 // ここから設定格納ディレクトリ解禁
\r
768 setting.loadConfig();
\r
770 RUNTIME.addShutdownHook(new Thread(){
\r
773 logger().info("シャットダウン処理に入ります…");
\r
774 System.out.flush();
\r
775 System.err.flush();
\r
778 RUNTIME.runFinalization(); // 危険?
\r
786 GUIUtils.replaceEventQueue();
\r
788 boolean hasError = false;
\r
790 EventQueue.invokeAndWait(new Runnable(){
\r
796 }catch(InvocationTargetException e){
\r
797 logger().fatal("アプリケーション初期化に失敗しました", e);
\r
798 e.printStackTrace(System.err);
\r
800 }catch(InterruptedException e){
\r
801 logger().fatal("アプリケーション初期化に失敗しました", e);
\r
802 e.printStackTrace(System.err);
\r
805 if(splashWindow != null){
\r
806 splashWindow.setVisible(false);
\r
807 splashWindow.dispose();
\r
808 splashWindow = null;
\r
812 if(hasError) exit(1);
\r