編集中)

このページについて

チュートリアル通りに11ページまで作業を進めていったところ、アプリケーションのLaunch後にNullPointerExceptionが発生し、期待通りの内容が表示されない現象となった。このページは問題の解決などを通してわかったことの覚書である。

結論

まだ推測の域を出ない箇所もあるものの、再現に使用しているEclipseのバージョン(4.3 Kepler)とチュートリアルのバージョン(不明。最初のページに2007年とあることから3.xと思われる)が異なることから発生したものと考えられる。そこで、エディタの内容を表示するコードの記載箇所を以下の対策に示すとおりに変えることで、チュートリアルと同じ結果が得られるようになった。急ぐ場合はそちらを参照する。

教訓&感想

解決にあたりいろいろな教訓や感想が得られた。見返すとアタリマエのことばかりだが、念のため書き留めておく。それは、
  • ステップイン/ステップアウト/ブレークポイントなどなど、デバッガが提供している手段はすべて使う
    • ステップオーバーでI/Fだけ舐めることは全体像を把握するのに有効と考える一方、ステップインでどんどん低階層にアクセスすると内部構造など色々見えてきて、解決策も練りやすい場合もあった
    • こんな時オープンソースだと、まさにソースがオープンになっていて、なぜエラーになるのかの検討の際に助かるだけでなく、また他人の書いたコードは勉強にもなる点で、いろいろ興味深い*1
    • 解決策へのヒントは主にコールスタックの読み解きから得られていた。が、今回のNullPointerExceptionのような例外においては、コールスタックだけではなかなか原因を特定しにくいケースであった。そこで、例外発生直前のコードにブレークポイントを置き、ステップインとステップアウトを駆使し、内部で何が起きているのか大体つかめるようになった
  • ドキュメントをよく読む
    • 扱いに不慣れなクラスであってもドキュメントを読むことで、使い方や構造が透けて見えてくることがある
    • 今回はバージョン違いが原因のトラブルと思われるが、ライブラリのバージョンアップに伴いこれまで使用していたメソッドが非推奨となるケース、というのはJava自身を含めてよくある話
  • イベントリスナーなど呼び出しタイミングに依存している現象は呼び出し順を把握する
    • 呼び出し順の把握には、ブレークポイントやロギングを有効に使う
    • 低レベルな処理や割り込みを多用する組み込みソフト開発ではこれはかなり当たり前。特にモニターがついていない機器ではこれ+シリアル通信もしくはJTAGなどなくしてデバッグは困難。呼び出し順やこのタイミングで○○する、などは書いていて組み込みっぽい感じがしてちょっと親近感がわいた
である

対策

NullPointerExceptionが発生したコードは、自動生成されたクラスApplicationWorkbenchAdvisorのpostStartupメソッドに追加実装した部分である。
public class ApplicationWorkbenchAdvisor extends WorkbenchAdvisor { 
 (...) 
 @Override 
 public void postStartup() { 
  try { 
   IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); 
   page.openEditor(new MyEditorInput("TutoGEF"), MyGraphicalEditor.ID, false); 
  } catch (Exception e) { 
   e.printStackTrace(); 
  } 
 }
}
チュートリアルでやりたいことは、サンプルコードから察するに、表示されたウィンドウにエディタクラスを割り当て、エディタに定義された内容を表示すること、である。そこで以下のように、
public class ApplicationWorkbenchWindowAdvisor extends WorkbenchWindowAdvisor {
 (...) 
    public void postWindowOpen(){
    	try {
    		IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
    		page.openEditor(new MyEditorInput("TutoGEF"), MyGraphicalEditor.ID, false);   
    	} catch (Exception e) { 
    		e.printStackTrace(); 
    	}
    }
}
WorkbenchWindowAdvisorクラスを継承したApplicationWorkbenchWindowAdvisorクラスに、WorkbenchWindowAdvisorクラスのpostWindowOpenメソッドをオーバライドし、上記のtry〜catch節部分をまるごとコピーする。これにより、まったく同じコードであるにもかかわらず、NullPointerExceptionは発生しない。なお、エラーに関する詳細は以下の検討で詳細に記載する。

検討

編集中)

NullPointerExceptionが発生した箇所

どのメソッドで発生したのか特定するために、
    		IWorkbench bench = PlatformUI.getWorkbench();
    		IWorkbenchWindow bw = bench.getActiveWorkbenchWindow();    	
    		IWorkbenchPage page = bw.getActivePage();
    		page.openEditor(new MyEditorInput("TutoGEF"), MyGraphicalEditor.ID, false);  
のように1メソッドずつ分解、最初の行にブレークポイントを張り、ステップオーバで戻り値を確認していった。その結果、bench.getActiveWorkbenchWindow()の戻り値がnullとなり、次の行でいわばnull.getActivePage()を実行しようとして、NullPointerExceptionが発生したのだろう。
次は、なぜgetActiveWorkbenchWindow()の戻り値がnullとなるのかステップインして確かめてみたところ、
public IWorkbenchWindow getActiveWorkbenchWindow() {
		// Return null if called from a non-UI thread.
		// This is not spec'ed behaviour and is misleading, however this is how
		// it
		// worked in 2.1 and we cannot change it now.
		// For more details, see [Bug 57384] [RCP] Main window not active on
		// startup
		if (Display.getCurrent() == null || !initializationDone) {
			return null;
		}

		// the source providers try to update again during shutdown
		if (windowsClosed) {
			return null;
		}

		// rendering engine not available, can't make workbench windows, see bug
		// 320932
		if (e4Context.get(IPresentationEngine.class) == null) {
			return null;
		}

		MWindow activeWindow = application.getSelectedElement();
		if (activeWindow == null && !application.getChildren().isEmpty()) {
			activeWindow = application.getChildren().get(0);
		}

		// We can't return a window with no widget...it's in the process
		// of closing...see Bug 379717
		if (activeWindow != null && activeWindow.getWidget() == null) {
			return null;
		}

		return createWorkbenchWindow(getDefaultPageInput(), getPerspectiveRegistry()
				.findPerspectiveWithId(getPerspectiveRegistry().getDefaultPerspective()),
				activeWindow, false);
	}
e4Context.get(IPresentationEngine.class)の戻り値がnullのためnullが返却されたところまでわかった。さらに、e4Context.get()やIPresentationEngine.classにステップインしてみたが、さっぱりわからない。get()しようとしているということは、これより以前のどこかのタイミングでsetするコードを通過している必要が有ることは想像に難くないものの、どこを探せばいいのかも分からない。キーワードはIPresentationEngineとe4ContextでEclipseのヘルプを中心に検索したが、まったくヒントが見当たらない。

NullPointerExceptionが発生しない箇所

解決の方向性が見えずに途方に暮れつつも、GEF習得にあたり並行して検討していたこの記事を読むと、チュートリアルとは違うやり方でエディタ画面を表示していることに気づいた。そのやり方とは、
public class ApplicationActionBarAdvisor extends ActionBarAdvisor {

	private NewAction newAction;
	public ApplicationActionBarAdvisor(IActionBarConfigurer configurer) {
		super(configurer);
	}

	protected void makeActions(IWorkbenchWindow window) {
		newAction =  new NewAction();
		newAction.setWorkbenchWindow(window); 
	}

	protected void fillMenuBar(IMenuManager menuBar) {
	}
	
	@Override
	protected void fillCoolBar(ICoolBarManager coolBar) {
		// クールバーの取得
		ToolBarManager toolBar = new ToolBarManager(SWT.FLAT);
		// アクションの追加
		toolBar.add(new Separator());
		toolBar.add(newAction);
		toolBar.add(new Separator());
		coolBar.add(toolBar);

	}
}
public class NewAction extends Action {

	private IWorkbenchWindow workbenchWindow;

	public NewAction() {

	}

	public void dispose() {
		workbenchWindow = null;
	}

	public void run() {
		IWorkbenchPage page = workbenchWindow.getActivePage();
		try {
			page.openEditor(new MyEditorInput("TutoGEF"), MyGraphicalEditor.ID, false); 
		} catch (Exception e) { 
			e.printStackTrace(); 
		}
	}

	public void setWorkbenchWindow(IWorkbenchWindow window) {
		workbenchWindow = window;
	}
}
とあるように、ActionBarAdvisorクラスのmakeActionsメソッドの引数にあるIWorkbenchWindowのオブジェクトをnewAction.setWorkbenchWindow(window)でActionクラスの変数に記憶しておき、newActionのボタン押下時にIWorkbenchWindowのオブジェクトを使用して、エディタ画面を表示させるというものである。

仮説

上記の状況から、NullPointerExceptionを返却した

編集中)

チュートリアル通りとしたい場合、アクションから
にある

仮説

For that, we will overload the postStartup() method in ApplicationWorkbenchAdvisor.で
通りにやっていくとnullポインタ
ブレークポイントを張り、デバッガで呼び出し順を確認したところ、
  1. WorkbenchAdvisorのpostStartupが呼ばれる
    1. IWorkbench bench = PlatformUI.getWorkbench();で、getWorkbench()の戻り値は、id*265のIWorkbenchオブジェクト
    2. IWorkbenchWindow bw = bench.getActiveWorkbenchWindow();で、bench.getActiveWorkbenchWindow()の戻り値はnull。nullではどうしようもなくなる…
  2. ActionBarAdvisorのmakeActionsが呼ばれる
    1. 引数であるIWorkbenchWindowのオブジェクトのidは1046
    2. 引数であるIWorkbenchWindowのオブジェクトからWorkbenchAdvisorへの参照ができるようだ。idも65で同一
となった。
  • IWorkbenchのメソッドで、アクティブでなくていいので、WorkbenchWindowのオブジェクトを取得するメソッドがないか探したところ、getWorkbenchWindowsなるメソッドがあった。まだ呼び出していないものの、

リンク

コメントをかく


「http://」を含む投稿は禁止されています。

利用規約をご確認のうえご記入下さい

管理人/副管理人のみ編集できます