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