Eclipseのプラグイン開発でクラスやメソッドを検索して結果を表示する方法について。
| 
 | 
ワークスペースやプロジェクト内のクラスやメソッドを検索して結果を“検索結果ビュー”に表示したい場合は、NewSearchUIを使う。
(SearchUIというクラスもあったようだが、Eclipse3.0以降はNewSearchUIを使う)
import org.eclipse.search.ui.NewSearchUI;
NewSearchUIを使うには、「依存関係」の「必須プラグイン」で「org.eclipse.search」を追加する必要がある。
NewSearchUIを使って検索を実行するには、ISearchQueryを渡す必要がある。
import org.eclipse.search.ui.ISearchQuery; import org.eclipse.search.ui.NewSearchUI;
ISearchQuery query = new MySearchQuery(); // NewSearchUI.activateSearchResultView(); NewSearchUI.runQueryInBackground(query);
ISearchQueryでは、検索結果を保持するISearchResultを必要とする。
ISearchQueryによって検索を実施し、ISearchResultに結果を追加していく。
検索結果ビューは、ISearchResultの内容を表示することになる。表示にはISearchResultPageが使われる。
例として、Javaの要素(ITypeやIMethod)を検索結果として保持するISearchResultクラスを作ってみる。
import org.eclipse.core.resources.IFile; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IParent; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.search.ui.ISearchQuery; import org.eclipse.search.ui.text.AbstractTextSearchResult; import org.eclipse.search.ui.text.IEditorMatchAdapter; import org.eclipse.search.ui.text.IFileMatchAdapter; import org.eclipse.search.ui.text.Match; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IFileEditorInput;
public class MySearchResult extends AbstractTextSearchResult implements IFileMatchAdapter, IEditorMatchAdapter {
	private final ISearchQuery query;
	public MySearchResult(ISearchQuery query) {
		this.query = query;
	}
	// 検索結果ビューに表示されるタイトル
	@Override
	public String getLabel() {
		return MessageFormat.format("{0} {1} found", query.getLabel(), getMatchCount());
	}
	@Override
	public String getTooltip() {
		return getLabel();
	}
	@Override
	public ImageDescriptor getImageDescriptor() {
		return null;
	}
	@Override
	public ISearchQuery getQuery() {
		return query;
	}
	@Override
	public IEditorMatchAdapter getEditorMatchAdapter() {
		return this;
	}
	@Override
	public IFileMatchAdapter getFileMatchAdapter() {
		return this;
	}
	// IFileMatchAdapter
	@Override
	public Match[] computeContainedMatches(AbstractTextSearchResult result, IFile file) {
		return getMatches(file);
	}
	@Override
	public IFile getFile(Object element) {
		if (element instanceof IJavaElement) {
			IJavaElement javaElement = (IJavaElement) element;
			return (IFile) javaElement.getResource();
		}
		if (element instanceof IEditorPart) {
			IEditorPart editor = (IEditorPart) element;
			IEditorInput input = editor.getEditorInput();
			if (input instanceof IFileEditorInput) {
				return ((IFileEditorInput) input).getFile();
			}
		}
		return null;
	}
	// IEditorMatchAdapter
	@Override
	public boolean isShownInEditor(Match match, IEditorPart editor) {
		IFile editorFile = getFile(editor);
		if (editorFile == null) {
			return false;
		}
		IFile file = getFile(match.getElement());
		return editorFile.equals(file);
	}
	@Override
	public Match[] computeContainedMatches(AbstractTextSearchResult result, IEditorPart editor) {
		IFile file = getFile(editor);
		return getMatches(file);
	}
	protected Match[] getMatches(IFile file) {
		IJavaElement element = JavaCore.create(file);
		Set<Match> set = new HashSet<Match>();
		collectMatches(set, element);
		return set.toArray(new Match[set.size()]);
	}
	private void collectMatches(Set<Match> set, IJavaElement element) {
		if (element == null) {
			return;
		}
		Match[] matches = super.getMatches(element);
		for (Match match : matches) {
			set.add(match);
		}
		if (element instanceof IParent) {
			try {
				IJavaElement[] children = ((IParent) element).getChildren();
				for (IJavaElement child : children) {
					collectMatches(set, child);
				}
			} catch (JavaModelException e) {
				// do nothing
			}
		}
	}
}
ISearchResultインターフェースにはAbstractTextSearchResultというクラスが用意されているので、それを継承する。
また、IFileMatchAdapterおよびIEditorMatchAdapterインターフェースも実装する。
これらは、検索結果ビューからエディターへジャンプしたりエディター上にマークを付けたりするのに関係していると思われる。
getMatchesメソッドは、エディターやファイルに存在する検索結果一覧を返すもの。
デフォルトでは、親クラスの中で「Matchに入れた要素」をキーとしてMapが用意されていて、そこに登録されているものを返す。
が、今回の例では、ISearchQueryの中でIJavaElementをMatchに入れているので、IFileやIEditorPartからIJavaElementを探す必要がある。
例として、Javaの要素(ITypeやIMethod)を検索するISearchQueryクラスを作ってみる。
import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Status; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.search.IJavaSearchConstants; import org.eclipse.jdt.core.search.IJavaSearchScope; import org.eclipse.jdt.core.search.SearchEngine; import org.eclipse.jdt.core.search.SearchMatch; import org.eclipse.jdt.core.search.SearchParticipant; import org.eclipse.jdt.core.search.SearchPattern; import org.eclipse.jdt.core.search.SearchRequestor; import org.eclipse.search.ui.ISearchQuery; import org.eclipse.search.ui.text.Match;
public class MySearchQuery implements ISearchQuery {
private MySearchResult searchResult;
	@Override
	public IStatus run(IProgressMonitor monitor) throws OperationCanceledException {
		final MySearchResult result = getSearchResult();
		result.removeAll(); // クエリーは再実行されることがあるので、以前の検索結果をクリアする
		// SearchEngineの呼び出し
		SearchPattern pattern = createSearchPattern();
		SearchParticipant[] participants = { SearchEngine.getDefaultSearchParticipant() };
		IJavaSearchScope scope = SearchEngine.createWorkspaceScope();
		SearchRequestor requestor = new SearchRequestor() {
			@Override
			public void acceptSearchMatch(SearchMatch match) throws CoreException {
				// 見つかった位置をISearchResultに追加
				IJavaElement element = (IJavaElement) match.getElement();
				int offset = match.getOffset();
				int length = match.getLength();
				result.addMatch(new Match(element, offset, length));
			}
		};
		try {
			new SearchEngine().search(pattern, participants, scope, requestor, monitor);
		} catch (CoreException e) {
			return e.getStatus();
		}
		return Status.OK_STATUS;
	}
	// SearchPatternの作成
	private SearchPattern createSearchPattern() {
		String pattern = "com.example.modelgen.dmdl.model.SalesDetail.getSalesDateTime()"; // 探すメソッド
		int searchFor = IJavaSearchConstants.METHOD;
		int limitTo = IJavaSearchConstants.REFERENCES;
		int matchRule = SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE | SearchPattern.R_ERASURE_MATCH;
		return SearchPattern.createPattern(pattern, searchFor, limitTo, matchRule);
	}
	// 検索実行中に表示されるタイトル
	@Override
	public String getLabel() {
		return "example search";
	}
	@Override
	public boolean canRerun() {
		return true;
	}
	@Override
	public boolean canRunInBackground() {
		return true;
	}
	@Override
	public MySearchResult getSearchResult() {
		if (searchResult == null) {
			searchResult = new MySearchResult(this);
		}
		return searchResult;
	}
}
NewSearchUIを使って、検索を実行する。
import org.eclipse.search.ui.NewSearchUI;
MySearchQuery query = new MySearchQuery(); // NewSearchUI.activateSearchResultView(); NewSearchUI.runQueryInBackground(query);
参考にしたソースではactivateSearchResultViewメソッドを呼び出していたが、呼び出さなくても大丈夫っぽい。
これで検索は実行されるのだが、実はこれだけでは検索結果ビューには何も表示されない^^;
独自のISearchResultを使っているので、そのクラス用の結果を表示するISearchResultPageを作成しておく必要がある。
検索結果ビューに検索結果を表示するには、ISearchResultに応じた検索結果表示ページ(ISearchResultPage)が必要となる。
import org.eclipse.search.ui.ISearchResultPage;
ただ、これを実装するのはけっこう大変そうなので、Javaの要素を表示する場合はJavaSearchResultPageを使ってしまうのが楽。
(これはインターナルなクラスなので、使うと警告が出てしまうけれども)
ISearchResultとISearchResultPageの紐付けは、plugin.xmlに記述する。
   <extension
         point="org.eclipse.search.searchResultViewPages">
      <viewPage
            id="com.example.ui.MySearchResultPage"
            searchResultClass="com.example.ui.search.MySearchResult"
            helpContextId="org.eclipse.jdt.ui.java_search_result"
            class="org.eclipse.jdt.internal.ui.search.JavaSearchResultPage">
      </viewPage>
   </extension>
ISearchResultをFileSearchPageに紐付ける場合、そのISearchResult具象クラスはFileSearchResultを継承していないといけない。[2015-04-11]
FileSearchResultを継承していないと、以下のような例外が発生する。
java.lang.NullPointerException at org.eclipse.search.internal.ui.text.FileTreeContentProvider.getChildren(FileTreeContentProvider.java:177) at org.eclipse.search.internal.ui.text.FileTreeContentProvider.getElements(FileTreeContentProvider.java:47) at org.eclipse.jface.viewers.StructuredViewer.getRawChildren(StructuredViewer.java:1010) at org.eclipse.jface.viewers.ColumnViewer.getRawChildren(ColumnViewer.java:721) at org.eclipse.jface.viewers.AbstractTreeViewer.getRawChildren(AbstractTreeViewer.java:1351) at org.eclipse.jface.viewers.TreeViewer.getRawChildren(TreeViewer.java:391) at org.eclipse.jface.viewers.StructuredViewer.getFilteredChildren(StructuredViewer.java:917) at org.eclipse.jface.viewers.AbstractTreeViewer.getSortedChildren(AbstractTreeViewer.java:620) at org.eclipse.jface.viewers.AbstractTreeViewer$1.run(AbstractTreeViewer.java:820) at org.eclipse.swt.custom.BusyIndicator.showWhile(BusyIndicator.java:70) at org.eclipse.jface.viewers.AbstractTreeViewer.createChildren(AbstractTreeViewer.java:797) at org.eclipse.jface.viewers.TreeViewer.createChildren(TreeViewer.java:644) at org.eclipse.jface.viewers.AbstractTreeViewer.createChildren(AbstractTreeViewer.java:768) at org.eclipse.jface.viewers.AbstractTreeViewer.internalInitializeTree(AbstractTreeViewer.java:1548) at org.eclipse.jface.viewers.TreeViewer.internalInitializeTree(TreeViewer.java:833) at org.eclipse.jface.viewers.AbstractTreeViewer$5.run(AbstractTreeViewer.java:1532) at org.eclipse.jface.viewers.StructuredViewer.preservingSelection(StructuredViewer.java:1443) at org.eclipse.jface.viewers.TreeViewer.preservingSelection(TreeViewer.java:403) at org.eclipse.jface.viewers.StructuredViewer.preservingSelection(StructuredViewer.java:1404) at org.eclipse.jface.viewers.AbstractTreeViewer.inputChanged(AbstractTreeViewer.java:1525) at org.eclipse.jface.viewers.ContentViewer.setInput(ContentViewer.java:280) at org.eclipse.jface.viewers.StructuredViewer.setInput(StructuredViewer.java:1690) at org.eclipse.search.ui.text.AbstractTextSearchViewPage.connectViewer(AbstractTextSearchViewPage.java:910) at org.eclipse.search.ui.text.AbstractTextSearchViewPage.setInput(AbstractTextSearchViewPage.java:835) at org.eclipse.search2.internal.ui.SearchView.internalShowSearchPage(SearchView.java:408) at org.eclipse.search2.internal.ui.SearchView.showSearchResult(SearchView.java:374) at org.eclipse.search2.internal.ui.SearchViewManager.showNewSearchQuery(SearchViewManager.java:71) at org.eclipse.search2.internal.ui.SearchViewManager$1.queryAdded(SearchViewManager.java:48) at org.eclipse.search2.internal.ui.QueryManager.fireAdded(QueryManager.java:93) at org.eclipse.search2.internal.ui.QueryManager.addQuery(QueryManager.java:70) at org.eclipse.search2.internal.ui.InternalSearchUI.addQuery(InternalSearchUI.java:309) at org.eclipse.search2.internal.ui.InternalSearchUI.runSearchInBackground(InternalSearchUI.java:167) at org.eclipse.search.ui.NewSearchUI.runQueryInBackground(NewSearchUI.java:132) at org.eclipse.search.ui.NewSearchUI.runQueryInBackground(NewSearchUI.java:105)
	public Object[] getChildren(Object parentElement) {
		Set children= (Set) fChildrenMap.get(parentElement);	←fChildrenMapがnullなので、NullPointerException
〜
	}
fChildrenMapの初期化は、FileSearchResultの時しか行われないようになっているorz
	public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
		if (newInput instanceof FileSearchResult) {
			initialize((FileSearchResult) newInput);
		}
	}
	private synchronized void initialize(AbstractTextSearchResult result) {
〜
		fChildrenMap= new HashMap();
〜
	}