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.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
39 * Jindolf スタートアップクラス。
\r
42 * アプリ開始はstaticメソッド{@link #main(String[])}呼び出しから。
\r
44 public final class Jindolf{
\r
46 /** 実行に最低限必要なJREの版数。 */
\r
47 public static final String MINIMUM_JREVER = "1.5";
\r
51 public static final Class<?> SELF_KLASS;
\r
53 public static final Package SELF_PACKAGE;
\r
54 /** ランタイムPackage。 */
\r
55 public static final Package JRE_PACKAGE;
\r
57 public static final Runtime RUNTIME;
\r
59 public static final SecurityManager SEC_MANAGER;
\r
61 public static final ClassLoader LOADER;
\r
64 /** クラスロード時のナノカウント。 */
\r
65 public static final long NANOCT_LOADED;
\r
66 /** クラスロード時刻(エポックmsec)。 */
\r
67 public static final long EPOCHMS_LOADED;
\r
71 public static final String TITLE;
\r
73 public static final String VERSION;
\r
75 public static final String AUTHOR;
\r
77 public static final String COPYRIGHT;
\r
79 public static final String LICENSE;
\r
81 public static final String CONTACT;
\r
83 public static final String DEBUT;
\r
84 /** その他、何でも書きたいこと。 */
\r
85 public static final String COMMENT;
\r
87 public static final String ID;
\r
90 private static final LogWrapper COMMON_LOGGER;
\r
93 private static final AtomicBoolean INVOKE_FLAG;
\r
96 private static final String RES_LOGOICON =
\r
97 "resources/image/logo.png";
\r
99 private static OptionInfo option;
\r
100 private static AppSetting setting;
\r
102 /** バージョン定義リソース。 */
\r
103 private static final String RES_VERDEF = "resources/version.properties";
\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
112 ClassLoader thisLoader;
\r
114 thisLoader = SELF_KLASS.getClassLoader();
\r
115 }catch(SecurityException e){
\r
118 LOADER = thisLoader;
\r
120 if( ! JRE_PACKAGE.isCompatibleWith(MINIMUM_JREVER) ){
\r
121 String jreInstalled;
\r
123 jreInstalled = System.getProperty("java.home");
\r
124 }catch(SecurityException e){
\r
125 jreInstalled = "※インストール位置不明";
\r
128 "今このプログラム " + SELF_KLASS.getName() + " は\n"
\r
129 +"[ " + jreInstalled
\r
131 +" JRE" + JRE_PACKAGE.getSpecificationVersion()
\r
132 +" の実行環境で実行されようとしました。\n"
\r
134 +" JRE" + MINIMUM_JREVER
\r
135 + " 以降の実行環境が必要です。\n"
\r
136 +"おそらく http://www.java.com/ などからの"
\r
139 errorDialog("実行系の不備", errmsg);
\r
146 NANOCT_LOADED = System.nanoTime();
\r
147 EPOCHMS_LOADED = System.currentTimeMillis();
\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
159 +"\u0020"+ "Ver." + VERSION
\r
160 +"\u0020"+ COPYRIGHT
\r
161 +"\u0020"+ "("+ LICENSE +")";
\r
163 Logger jre14Logger = Logger.getLogger(SELF_PACKAGE.getName());
\r
164 COMMON_LOGGER = new LogWrapper(jre14Logger);
\r
166 INVOKE_FLAG = new AtomicBoolean(false);
\r
177 assert this.getClass() == SELF_KLASS;
\r
184 * @return 起動オプション情報
\r
186 public static OptionInfo getOptionInfo(){
\r
194 public static AppSetting getAppSetting(){
\r
199 * エラーダイアログをビットマップディスプレイに出現させる。
\r
200 * メインウィンドウが整備されるまでの間だけ一時的に使う。
\r
201 * 努力目標:なるべく昔のJRE環境でも例外無く動くように。
\r
202 * @param title タイトル
\r
203 * @param message メッセージ
\r
205 private static void errorDialog(String title, String message){
\r
206 System.err.println(message);
\r
207 System.err.flush();
\r
209 if( ! JRE_PACKAGE.isCompatibleWith("1.2") ){
\r
213 if( JRE_PACKAGE.isCompatibleWith("1.4")
\r
214 && GraphicsEnvironment.isHeadless()){
\r
218 JOptionPane.showMessageDialog(null,
\r
221 JOptionPane.ERROR_MESSAGE);
\r
227 * リソース上のパッケージ定義プロパティをロードする。
\r
228 * MANIFEST.MFが参照できない実行環境での代替品。
\r
229 * @param klass パッケージを構成する任意のクラス
\r
232 private static Properties loadVersionDefinition(Class klass){
\r
233 Properties result = new Properties();
\r
235 InputStream istream = klass.getResourceAsStream(RES_VERDEF);
\r
237 result.load(istream);
\r
238 }catch(IOException e){
\r
243 }catch(IOException e){
\r
253 * このクラスのパッケージのパッケージ情報を取得する。
\r
254 * MANIFEST.MFが参照できない実行環境での代替品。
\r
255 * @param prop プロパティ
\r
256 * @param prefix 接頭辞
\r
257 * @param defValue 見つからなかった場合のデフォルト値
\r
260 public static String getPackageInfo(Properties prop,
\r
263 return getPackageInfo(prop, SELF_PACKAGE, prefix, defValue);
\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
275 public static String getPackageInfo(Properties prop,
\r
279 String propName = prefix + pkg.getName();
\r
280 String result = prop.getProperty(propName, defValue);
\r
285 * Jindolf の実行が可能なGUI環境でなければ、即時VM実行を終了する。
\r
287 private static void checkGUIEnvironment(){
\r
288 if(GraphicsEnvironment.isHeadless()){
\r
289 System.err.println(
\r
291 + " はGUI環境と接続できませんでした");
\r
295 dispEnv = System.getenv("DISPLAY");
\r
296 }catch(SecurityException e){
\r
301 if(dispEnv != null){
\r
302 System.err.println("環境変数 DISPLAY : " + dispEnv);
\r
313 * ※ 非Unicode系の開発環境を使いたい人は適当に無視してね。
\r
315 private static void checkCompileError(){
\r
318 +"正しくコンパイルされていないかも。\n"
\r
319 +"あなたは今、オリジナル開発元の意図しない文字コード環境で"
\r
320 +"コンパイルされたプログラムを起動しようとしているよ。\n"
\r
322 +"どのような文字コード変換が行われたか認識しているかな?\n"
\r
323 +"コンパイルオプションで正しい文字コードを指定したかな?";
\r
328 || '\\' != 0x005c // バックスラッシュ
\r
329 || '¥' != 0x00a5 // 半角円通貨
\r
331 || '�' != 0xfffd // Unicode専用特殊文字
\r
333 JOptionPane.showMessageDialog(null,
\r
336 JOptionPane.ERROR_MESSAGE);
\r
343 * MANIFEST.MFパッケージ定義エラーの検出。
\r
344 * ビルド前にMANIFEST自動生成Antタスク「manifest」を忘れてないかい?
\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
351 String errmsg = null;
\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
367 if(errmsg != null){
\r
368 JOptionPane.showMessageDialog(null,
\r
371 JOptionPane.ERROR_MESSAGE);
\r
379 * 標準出力端末にヘルプメッセージ(オプションの説明)を表示する。
\r
381 private static void showHelpMessage(){
\r
382 System.out.flush();
\r
383 System.err.flush();
\r
385 CharSequence helpText = CmdOption.getHelpText();
\r
386 System.out.print(helpText);
\r
388 System.out.flush();
\r
389 System.err.flush();
\r
395 * スプラッシュウィンドウを作成する。
\r
396 * JRE1.6以降では呼ばれないはず。
\r
397 * @return 未表示のスプラッシュウィンドウ。
\r
399 private static Window createSplashWindow(){
\r
400 Window splashWindow = new JWindow();
\r
402 URL url = getResource(RES_LOGOICON);
\r
403 ImageIcon logo = new ImageIcon(url);
\r
404 JLabel splashLabel = new JLabel(logo);
\r
406 splashWindow.add(splashLabel);
\r
407 splashWindow.pack();
\r
408 splashWindow.setLocationRelativeTo(null); // locate center
\r
410 return splashWindow;
\r
415 * @param useConsoleLog trueならConsoleHandlerを使う。
\r
417 private static void initLogging(boolean useConsoleLog){
\r
418 boolean hasPermission = hasLoggingPermission();
\r
420 if( ! hasPermission){
\r
421 System.out.println(
\r
423 + "ログ設定を変更できませんでした" );
\r
426 Logger jre14Logger = COMMON_LOGGER.getJre14Logger();
\r
429 jre14Logger.setUseParentHandlers(false);
\r
430 Handler pileHandler = new PileHandler();
\r
431 jre14Logger.addHandler(pileHandler);
\r
434 if(hasPermission && useConsoleLog){
\r
435 Handler consoleHandler = new ConsoleHandler();
\r
436 jre14Logger.addHandler(consoleHandler);
\r
443 * ログ操作のアクセス権があるか否か判定する。
\r
444 * @return アクセス権があればtrue
\r
446 public static boolean hasLoggingPermission(){
\r
447 if(SEC_MANAGER == null) return true;
\r
449 Permission logPermission = new LoggingPermission("control", null);
\r
451 SEC_MANAGER.checkPermission(logPermission);
\r
452 }catch(SecurityException e){
\r
460 * 起動時の諸々の情報をログ出力する。
\r
462 private static void dumpBootInfo(){
\r
463 DateFormat dform = DateFormat.getDateTimeInstance();
\r
464 NumberFormat nform = NumberFormat.getNumberInstance();
\r
468 + dform.format(new Date(EPOCHMS_LOADED))
\r
470 + SELF_KLASS.getName() + " としてロードされました。 " );
\r
472 logger().info("Initial Nano-Count : " + nform.format(NANOCT_LOADED));
\r
476 + nform.format(RUNTIME.maxMemory()) + " Byte"
\r
478 + nform.format(RUNTIME.totalMemory()) + " Byte");
\r
480 logger().info("\n" + EnvInfo.getVMInfo());
\r
482 if(getAppSetting().useConfigPath()){
\r
483 logger().info("設定格納ディレクトリに[ "
\r
484 + getAppSetting().getConfigPath().getPath()
\r
487 logger().info("設定格納ディレクトリは使いません。");
\r
490 if( JRE_PACKAGE.isCompatibleWith("1.6")
\r
491 && option.hasOption(CmdOption.OPT_NOSPLASH) ){
\r
494 +"Jindolfの-nosplashオプションは無効です。"
\r
495 + "Java実行系の方でスプラッシュ画面の非表示を"
\r
496 + "指示してください(おそらく空の-splash:オプション)" );
\r
499 if(LOADER == null){
\r
502 +"クラスローダを取得できませんでした");
\r
509 * 任意のクラス群に対して一括ロード/初期化を単一スレッドで順に行う。
\r
510 * どーしてもクラス初期化の順序に依存する障害が発生する場合や
\r
511 * クラス初期化のオーバーヘッドでGUIの操作性が損なわれるときなどにどうぞ。
\r
513 * @throws java.lang.LinkageError クラス間リンケージエラー。
\r
514 * @throws java.lang.ExceptionInInitializerError クラス初期化で異常
\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
524 java.net.HttpURLConnection.class,
\r
525 java.text.SimpleDateFormat.class,
\r
529 for(Object obj : classes){
\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
540 if(LOADER != null){
\r
541 Class.forName(className, true, LOADER);
\r
543 Class.forName(className);
\r
545 }catch(ClassNotFoundException e){
\r
546 logger().warn("クラスの明示的ロードに失敗しました", e);
\r
555 * AWTイベントディスパッチスレッド版スタートアップエントリ。
\r
557 private static void startGUI(){
\r
558 LandsModel model = new LandsModel();
\r
559 model.loadLandList();
\r
561 JFrame topFrame = buildMVC(model);
\r
563 GUIUtils.modifyWindowAttributes(topFrame, true, false, true);
\r
567 Dimension initGeometry =
\r
568 new Dimension(setting.initialFrameWidth(),
\r
569 setting.initialFrameHeight());
\r
570 topFrame.setSize(initGeometry);
\r
572 if( setting.initialFrameXpos() <= Integer.MIN_VALUE
\r
573 || setting.initialFrameYpos() <= Integer.MIN_VALUE ){
\r
574 topFrame.setLocationByPlatform(true);
\r
576 topFrame.setLocation(setting.initialFrameXpos(),
\r
577 setting.initialFrameYpos() );
\r
580 topFrame.setVisible(true);
\r
586 * モデル・ビュー・コントローラの結合。
\r
588 * @param model 最上位のデータモデル
\r
589 * @return アプリケーションのトップフレーム
\r
591 private static JFrame buildMVC(LandsModel model){
\r
592 ActionManager actionManager = new ActionManager();
\r
593 TopView topView = new TopView();
\r
595 Controller controller = new Controller(actionManager, topView, model);
\r
597 JFrame topFrame = controller.createTopFrame();
\r
603 * リソースからUTF-8で記述されたテキストデータをロードする。
\r
604 * @param resourceName リソース名
\r
606 * @throws java.io.IOException 入出力の異常。おそらくビルドミス。
\r
608 public static CharSequence loadResourceText(String resourceName)
\r
609 throws IOException{
\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
616 StringBuilder result = new StringBuilder();
\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
625 lineReader.close();
\r
632 * クラスローダを介してリソースからの入力を生成する。
\r
633 * @param name リソース名
\r
634 * @return リソースからの入力
\r
636 public static InputStream getResourceAsStream(String name){
\r
637 return SELF_KLASS.getResourceAsStream(name);
\r
641 * クラスローダを介してリソース読み込み用URLを生成する。
\r
642 * @param name リソース名
\r
645 public static URL getResource(String name){
\r
646 return SELF_KLASS.getResource(name);
\r
653 public static LogWrapper logger(){
\r
654 return COMMON_LOGGER;
\r
659 * ※おそらく随所でシャットダウンフックが起動されるはず。
\r
661 * @param exitCode 終了コード
\r
662 * @throws java.lang.SecurityException セキュリティ違反
\r
664 public static void exit(int exitCode) throws SecurityException{
\r
668 + "]でVMごとアプリケーションを終了します。" );
\r
669 RUNTIME.runFinalization();
\r
670 System.out.flush();
\r
671 System.err.flush();
\r
673 RUNTIME.exit(exitCode);
\r
674 }catch(SecurityException e){
\r
677 +"VMを終了させることができません。", e);
\r
684 * Jindolf のスタートアップエントリ。
\r
686 * @param args コマンドライン引数
\r
688 public static void main(final String[] args){
\r
690 boolean hasInvoked = ! INVOKE_FLAG.compareAndSet(false, true);
\r
692 String errmsg = "二度目以降の起動がキャンセルされました。";
\r
693 errorDialog("多重起動", errmsg);
\r
699 checkGUIEnvironment();
\r
701 // ここからGUIウィンドウとマウス解禁
\r
703 checkCompileError();
\r
704 checkPackageDefinition();
\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
714 + CmdOption.OPT_HELP.toHyphened()
\r
715 + "」を指定すると確認できます。" );
\r
716 Jindolf.RUNTIME.exit(1);
\r
721 if(option.hasOption(CmdOption.OPT_HELP)){
\r
727 if(option.hasOption(CmdOption.OPT_VERSION)){
\r
728 System.out.println(ID);
\r
733 // あらゆるSwingコンポーネント操作より前に必要。
\r
734 if(option.hasOption(CmdOption.OPT_BOLDMETAL)){
\r
735 // もの凄く日本語表示が汚くなるかもよ!注意
\r
736 UIManager.put("swing.boldMetal", Boolean.TRUE);
\r
738 UIManager.put("swing.boldMetal", Boolean.FALSE);
\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
750 setting = new AppSetting();
\r
751 setting.applyOptionInfo(option);
\r
753 if(option.hasOption(CmdOption.OPT_VMINFO)){
\r
754 System.out.println(EnvInfo.getVMInfo());
\r
757 initLogging(option.hasOption(CmdOption.OPT_CONSOLELOG));
\r
759 // Jindolf.exit()もここから解禁
\r
763 ConfigFile.setupConfigDirectory();
\r
764 ConfigFile.setupLockFile();
\r
765 // ここから設定格納ディレクトリ解禁
\r
767 setting.loadConfig();
\r
769 RUNTIME.addShutdownHook(new Thread(){
\r
772 logger().info("シャットダウン処理に入ります…");
\r
773 System.out.flush();
\r
774 System.err.flush();
\r
777 RUNTIME.runFinalization(); // 危険?
\r
785 GUIUtils.replaceEventQueue();
\r
787 boolean hasError = false;
\r
789 EventQueue.invokeAndWait(new Runnable(){
\r
795 }catch(Throwable e){
\r
796 logger().fatal("アプリケーション初期化に失敗しました", e);
\r
797 e.printStackTrace(System.err);
\r
800 if(splashWindow != null){
\r
801 splashWindow.setVisible(false);
\r
802 splashWindow.dispose();
\r
803 splashWindow = null;
\r
807 if(hasError) exit(1);
\r