Bug 438509 - Plugin references search won't show features

- search in CTRL+H "Plug-in Search" will also search inside features
"Included plug-ins"(searchFor=plugin, limitTo=references,
externalScope=all, scope=workspace)
- see screenshot of patch working attached to bugzilla
- found plugins are reported in search result as individual matches in
form "pluginId - featureId"
- double-clicking on search result match opens feature editor on
"Included plug-ins" page and select + reveals the matched plugin 
- using feature icon in search results

Change-Id: I15125406c980bf382bf6d6e7b77ba7367d03b0c0
Signed-off-by: Martin Karpisek <martin.karpisek@gmail.com>
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/search/PluginSearchOperation.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/search/PluginSearchOperation.java
index 5e13dd1..313d07e 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/search/PluginSearchOperation.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/search/PluginSearchOperation.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2012 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -7,15 +7,19 @@
  *
  * Contributors:
  *     IBM Corporation - initial API and implementation
+ *     Martin Karpisek <martin.karpisek@gmail.com> - Bug 438509
  *******************************************************************************/
 package org.eclipse.pde.internal.core.search;
 
 import java.util.ArrayList;
+import java.util.List;
 import java.util.regex.Pattern;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.SubMonitor;
 import org.eclipse.pde.core.IIdentifiable;
 import org.eclipse.pde.core.plugin.*;
+import org.eclipse.pde.internal.core.ifeature.IFeatureModel;
+import org.eclipse.pde.internal.core.ifeature.IFeaturePlugin;
 import org.eclipse.pde.internal.core.util.PatternConstructor;
 
 public class PluginSearchOperation {
@@ -30,14 +34,19 @@
 	}
 
 	public void execute(IProgressMonitor monitor) {
-		IPluginModelBase[] entries = fInput.getSearchScope().getMatchingModels();
-		SubMonitor subMonitor = SubMonitor.convert(monitor, entries.length);
+		IPluginModelBase[] plugins = fInput.getSearchScope().getMatchingModels();
+		IFeatureModel[] features = fInput.getSearchScope().getMatchingFeatureModels();
+		SubMonitor subMonitor = SubMonitor.convert(monitor, plugins.length + features.length);
 
-		for (IPluginModelBase candidate : entries) {
+		for (IPluginModelBase candidate : plugins) {
 			visit(candidate);
 			subMonitor.step(1);
 		}
 
+		for (IFeatureModel candidate : features) {
+			visit(candidate);
+			subMonitor.step(1);
+		}
 	}
 
 	private void visit(IPluginModelBase model) {
@@ -47,6 +56,13 @@
 		}
 	}
 
+	private void visit(final IFeatureModel model) {
+		final List<IIdentifiable> matches = findMatch(model);
+		for (int i = 0; i < matches.size(); i++) {
+			fCollector.accept(matches.get(i));
+		}
+	}
+
 	private ArrayList<IIdentifiable> findMatch(IPluginModelBase model) {
 		ArrayList<IIdentifiable> result = new ArrayList<>();
 		int searchLimit = fInput.getSearchLimit();
@@ -70,6 +86,19 @@
 		return result;
 	}
 
+	private List<IIdentifiable> findMatch(final IFeatureModel model) {
+		final List<IIdentifiable> result = new ArrayList<>();
+		int searchLimit = fInput.getSearchLimit();
+		switch (fInput.getSearchElement()) {
+		case PluginSearchInput.ELEMENT_PLUGIN:
+			if (searchLimit != PluginSearchInput.LIMIT_DECLARATIONS) {
+				findPluginReferences(model, result);
+			}
+			break;
+		}
+		return result;
+	}
+
 	private void findFragmentDeclaration(IPluginModelBase model, ArrayList<IIdentifiable> result) {
 		IPluginBase pluginBase = model.getPluginBase();
 		if (pluginBase instanceof IFragment && fPattern.matcher(pluginBase.getId()).matches()) {
@@ -96,6 +125,24 @@
 		}
 	}
 
+	/**
+	 * Search feature if any of its included plugins match the pattern search.
+	 *
+	 * @param model
+	 *            of feature
+	 * @param result
+	 *            will contain references to plugins included in feature
+	 *            matching the pattern
+	 */
+	private void findPluginReferences(final IFeatureModel model, final List<IIdentifiable> result) {
+		final IFeaturePlugin[] includedPlugins = model.getFeature().getPlugins();
+		for (IFeaturePlugin plugin : includedPlugins) {
+			if (fPattern.matcher(plugin.getId()).matches()) {
+				result.add(plugin);
+			}
+		}
+	}
+
 	private void findExtensionPointDeclarations(IPluginModelBase model, ArrayList<IIdentifiable> result) {
 		IPluginExtensionPoint[] extensionPoints = model.getPluginBase().getExtensionPoints();
 		for (int i = 0; i < extensionPoints.length; i++) {
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/search/PluginSearchScope.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/search/PluginSearchScope.java
index 8d91df3..7057d6b 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/search/PluginSearchScope.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/search/PluginSearchScope.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- *  Copyright (c) 2000, 2013 IBM Corporation and others.
+ *  Copyright (c) 2000, 2016 IBM Corporation and others.
  *  All rights reserved. This program and the accompanying materials
  *  are made available under the terms of the Eclipse Public License v1.0
  *  which accompanies this distribution, and is available at
@@ -7,13 +7,15 @@
  *
  *  Contributors:
  *     IBM Corporation - initial API and implementation
+ *     Martin Karpisek <martin.karpisek@gmail.com> - Bug 438509
  *******************************************************************************/
 package org.eclipse.pde.internal.core.search;
 
-import java.util.ArrayList;
-import java.util.HashSet;
+import java.util.*;
 import org.eclipse.pde.core.plugin.IPluginModelBase;
 import org.eclipse.pde.core.plugin.PluginRegistry;
+import org.eclipse.pde.internal.core.PDECore;
+import org.eclipse.pde.internal.core.ifeature.IFeatureModel;
 
 public class PluginSearchScope {
 
@@ -59,6 +61,13 @@
 			result.add(candidate);
 	}
 
+	protected final void addExternalModel(final IFeatureModel candidate, final List<IFeatureModel> result) {
+		if (externalScope == EXTERNAL_SCOPE_ALL)
+			result.add(candidate);
+		else if (externalScope == EXTERNAL_SCOPE_ENABLED && candidate.isEnabled())
+			result.add(candidate);
+	}
+
 	protected final void addWorkspaceModel(IPluginModelBase candidate, ArrayList<IPluginModelBase> result) {
 		if (workspaceScope == SCOPE_WORKSPACE) {
 			result.add(candidate);
@@ -67,6 +76,14 @@
 		}
 	}
 
+	protected final void addWorkspaceModel(final IFeatureModel candidate, final List<IFeatureModel> result) {
+		if (workspaceScope == SCOPE_WORKSPACE) {
+			result.add(candidate);
+		} else if (selectedResources.contains(candidate.getUnderlyingResource().getProject())) {
+			result.add(candidate);
+		}
+	}
+
 	public IPluginModelBase[] getMatchingModels() {
 		return addRelevantModels(PluginRegistry.getAllModels());
 	}
@@ -83,4 +100,19 @@
 		return result.toArray(new IPluginModelBase[result.size()]);
 	}
 
+	public IFeatureModel[] getMatchingFeatureModels() {
+		return addRelevantModels(PDECore.getDefault().getFeatureModelManager().getModels());
+	}
+
+	protected final IFeatureModel[] addRelevantModels(IFeatureModel[] models) {
+		final List<IFeatureModel> result = new ArrayList<>();
+		for (IFeatureModel model : models) {
+			if (model.getUnderlyingResource() != null) {
+				addWorkspaceModel(model, result);
+			} else {
+				addExternalModel(model, result);
+			}
+		}
+		return result.toArray(new IFeatureModel[result.size()]);
+	}
 }
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/feature/FeatureEditor.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/feature/FeatureEditor.java
index 69fd365..45e8840 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/feature/FeatureEditor.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/feature/FeatureEditor.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -8,6 +8,7 @@
  * Contributors:
  *     IBM Corporation - initial API and implementation
  *     EclipseSource Corporation - ongoing enhancements
+ *     Martin Karpisek <martin.karpisek@gmail.com> - Bug 438509
  *******************************************************************************/
 package org.eclipse.pde.internal.ui.editor.feature;
 
@@ -59,7 +60,27 @@
 		}
 	}
 
-	public static void openFeatureEditor(IFeatureModel model) {
+	/**
+	 * Opens Feature Editor on "Included Plug-ins" page and preselects the
+	 * featurePlugin.
+	 *
+	 * @param featurePlugin is included plug-in in feature
+	 */
+	public static void openFeatureEditor(final IFeaturePlugin featurePlugin) {
+		if (featurePlugin != null) {
+			IEditorPart editor = openFeatureEditor(featurePlugin.getModel());
+			// activate the page with plug-ins and preselect the requested
+			// featurePlugin
+			if (editor instanceof FeatureEditor) {
+				IFormPage page = ((FeatureEditor) editor).setActivePage(FeatureReferencePage.PAGE_ID);
+				page.selectReveal(featurePlugin);
+			}
+		} else {
+			Display.getCurrent().beep();
+		}
+	}
+
+	public static IEditorPart openFeatureEditor(IFeatureModel model) {
 		if (model != null) {
 			IResource resource = model.getUnderlyingResource();
 			try {
@@ -71,7 +92,7 @@
 					IFileStore store = EFS.getStore(file.toURI());
 					input = new FileStoreEditorInput(store);
 				}
-				IDE.openEditor(PDEPlugin.getActivePage(), input, IPDEUIConstants.FEATURE_EDITOR_ID, true);
+				return IDE.openEditor(PDEPlugin.getActivePage(), input, IPDEUIConstants.FEATURE_EDITOR_ID, true);
 			} catch (PartInitException e) {
 				PDEPlugin.logException(e);
 			} catch (CoreException e) {
@@ -80,7 +101,7 @@
 		} else {
 			Display.getCurrent().beep();
 		}
-
+		return null;
 	}
 
 	public FeatureEditor() {
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/feature/FeatureReferencePage.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/feature/FeatureReferencePage.java
index f9db7d8..9d7e281 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/feature/FeatureReferencePage.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/feature/FeatureReferencePage.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -7,9 +7,12 @@
  *
  * Contributors:
  *     IBM Corporation - initial API and implementation
+ *     Martin Karpisek <martin.karpisek@gmail.com> - Bug 438509
  *******************************************************************************/
 package org.eclipse.pde.internal.ui.editor.feature;
 
+import org.eclipse.jface.viewers.*;
+import org.eclipse.pde.internal.core.ifeature.IFeaturePlugin;
 import org.eclipse.pde.internal.ui.*;
 import org.eclipse.pde.internal.ui.editor.*;
 import org.eclipse.swt.layout.GridData;
@@ -92,4 +95,25 @@
 	public void setFocus() {
 		fPluginSection.setFocus();
 	}
+
+	@Override
+	public boolean selectReveal(final Object object) {
+		if (object instanceof IFeaturePlugin) {
+			// selecton has to be done by detecting item from content provider by id of feature
+			// and using that in new selection 
+			// because just using #setSelection(object) will not work
+			final IFeaturePlugin featurePlugin = (IFeaturePlugin) object;
+			final StructuredViewer fPluginViewer = fPluginSection.getStructuredViewerPart().getViewer();
+			final IStructuredContentProvider provider = (IStructuredContentProvider) fPluginViewer.getContentProvider();
+			for (Object o : provider.getElements(fPluginViewer.getInput())) {
+				final IFeaturePlugin fp = (IFeaturePlugin) o;
+
+				if (fp.getId().equals(featurePlugin.getId())) {
+					fPluginViewer.setSelection(new StructuredSelection(fp), true);
+					return true;
+				}
+			}
+		}
+		return false;
+	}
 }
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/PluginSearchQuery.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/PluginSearchQuery.java
index 77b4cd8..07d0b58 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/PluginSearchQuery.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/PluginSearchQuery.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2005, 2015 IBM Corporation and others.
+ * Copyright (c) 2005, 2016 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -7,11 +7,13 @@
  *
  * Contributors:
  *     IBM Corporation - initial API and implementation
+ *     Martin Karpisek <martin.karpisek@gmail.com> - Bug 438509
  *******************************************************************************/
 package org.eclipse.pde.internal.ui.search;
 
 import org.eclipse.core.runtime.*;
 import org.eclipse.pde.core.ISourceObject;
+import org.eclipse.pde.internal.core.ifeature.IFeaturePlugin;
 import org.eclipse.pde.internal.core.search.*;
 import org.eclipse.search.ui.ISearchQuery;
 import org.eclipse.search.ui.ISearchResult;
@@ -39,6 +41,10 @@
 					ISourceObject object = (ISourceObject) match;
 					result.addMatch(new Match(match, Match.UNIT_LINE, object.getStartLine() - 1, 1));
 				}
+				if (match instanceof IFeaturePlugin) {
+					IFeaturePlugin object = (IFeaturePlugin) match;
+					result.addMatch(new Match(object, Match.UNIT_LINE, -1, 1));
+				}
 			}
 		};
 		PluginSearchOperation op = new PluginSearchOperation(fSearchInput, collector);
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/PluginSearchResultPage.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/PluginSearchResultPage.java
index 0080767..23c4431 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/PluginSearchResultPage.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/PluginSearchResultPage.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- *  Copyright (c) 2005, 2015 IBM Corporation and others.
+ *  Copyright (c) 2005, 2016 IBM Corporation and others.
  *  All rights reserved. This program and the accompanying materials
  *  are made available under the terms of the Eclipse Public License v1.0
  *  which accompanies this distribution, and is available at
@@ -7,6 +7,7 @@
  *
  *  Contributors:
  *     IBM Corporation - initial API and implementation
+ *     Martin Karpisek <martin.karpisek@gmail.com> - Bug 438509
  *******************************************************************************/
 package org.eclipse.pde.internal.ui.search;
 
@@ -14,7 +15,9 @@
 import org.eclipse.jface.action.Separator;
 import org.eclipse.jface.viewers.*;
 import org.eclipse.pde.core.plugin.*;
+import org.eclipse.pde.internal.core.ifeature.IFeaturePlugin;
 import org.eclipse.pde.internal.ui.PDEPlugin;
+import org.eclipse.pde.internal.ui.editor.feature.FeatureEditor;
 import org.eclipse.pde.internal.ui.views.plugins.ImportActionGroup;
 import org.eclipse.pde.internal.ui.views.plugins.JavaSearchActionGroup;
 import org.eclipse.search.ui.text.Match;
@@ -27,6 +30,10 @@
 	class SearchLabelProvider extends LabelProvider {
 		@Override
 		public Image getImage(Object element) {
+			if (element instanceof IFeaturePlugin) {
+				return getImage(((IFeaturePlugin) element).getFeature().getModel());
+			}
+
 			return PDEPlugin.getDefault().getLabelProvider().getImage(element);
 		}
 
@@ -46,8 +53,16 @@
 				return extension.getPoint() + " - " + extension.getPluginBase().getId(); //$NON-NLS-1$
 			}
 
-			if (object instanceof IPluginExtensionPoint)
+			if (object instanceof IPluginExtensionPoint) {
 				return ((IPluginExtensionPoint) object).getFullId();
+			}
+
+			if (object instanceof IFeaturePlugin) {
+				final IFeaturePlugin featurePlugin = (IFeaturePlugin) object;
+				final String pluginId = featurePlugin.getId();
+				final String featureId = featurePlugin.getFeature().getId();
+				return pluginId + " - " + featureId; //$NON-NLS-1$
+			}
 
 			return PDEPlugin.getDefault().getLabelProvider().getText(object);
 		}
@@ -92,6 +107,11 @@
 
 	@Override
 	protected void showMatch(Match match, int currentOffset, int currentLength, boolean activate) throws PartInitException {
+		if (match.getElement() instanceof IFeaturePlugin) {
+			IFeaturePlugin featurePlugin = (IFeaturePlugin) match.getElement();
+			FeatureEditor.openFeatureEditor(featurePlugin);
+			return;
+		}
 		ManifestEditorOpener.open(match, activate);
 	}
 
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/SearchResult.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/SearchResult.java
index de86e5d..364c571 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/SearchResult.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/SearchResult.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- *  Copyright (c) 2005, 2015 IBM Corporation and others.
+ *  Copyright (c) 2005, 2016 IBM Corporation and others.
  *  All rights reserved. This program and the accompanying materials
  *  are made available under the terms of the Eclipse Public License v1.0
  *  which accompanies this distribution, and is available at
@@ -7,11 +7,10 @@
  *
  *  Contributors:
  *     IBM Corporation - initial API and implementation
+ *     Martin Karpisek <martin.karpisek@gmail.com> - Bug 438509
  *******************************************************************************/
 package org.eclipse.pde.internal.ui.search;
 
-import org.eclipse.search.ui.text.Match;
-
 import java.io.File;
 import java.util.ArrayList;
 import org.eclipse.core.resources.IFile;
@@ -22,6 +21,7 @@
 import org.eclipse.jface.text.IDocument;
 import org.eclipse.pde.core.plugin.IPluginObject;
 import org.eclipse.pde.internal.core.ICoreConstants;
+import org.eclipse.pde.internal.core.ifeature.IFeaturePlugin;
 import org.eclipse.pde.internal.ui.PDEPluginImages;
 import org.eclipse.pde.internal.ui.PDEUIMessages;
 import org.eclipse.search.ui.ISearchQuery;
@@ -65,8 +65,12 @@
 	@Override
 	public boolean isShownInEditor(Match match, IEditorPart editor) {
 		Object element = match.getElement();
-		if (element instanceof IPluginObject)
+		if (element instanceof IPluginObject) {
 			return isMatchContained(editor, (IPluginObject) element);
+		}
+		if (element instanceof IFeaturePlugin) {
+			return isMatchContained(editor, (IFeaturePlugin) element);
+		}
 		return false;
 	}
 
@@ -86,6 +90,14 @@
 					}
 				}
 			}
+			if (objects[i] instanceof IFeaturePlugin) {
+				IFeaturePlugin object = (IFeaturePlugin) objects[i];
+				if (isMatchContained(editor, object)) {
+					for (Match match : getMatches(object)) {
+						list.add(match);
+					}
+				}
+			}
 		}
 		return list.toArray(new Match[list.size()]);
 	}
@@ -95,16 +107,27 @@
 		return null;
 	}
 
-	protected boolean isMatchContained(IEditorPart editor, IPluginObject object) {
-		IFile resource = (IFile) editor.getEditorInput().getAdapter(IFile.class);
+	protected boolean isMatchContained(final IEditorPart editor, final IPluginObject object) {
+		return isMatchContained(editor, object.getModel().getUnderlyingResource(),
+				object.getModel().getInstallLocation());
+	}
+
+	protected boolean isMatchContained(final IEditorPart editor, final IFeaturePlugin object) {
+		return isMatchContained(editor, object.getModel().getUnderlyingResource(),
+				object.getModel().getInstallLocation());
+	}
+
+	protected boolean isMatchContained(final IEditorPart editor, final IResource underlyingResource,
+			final String installLocation) {
+		IFile resource = editor.getEditorInput().getAdapter(IFile.class);
 		if (resource != null) {
-			IResource objectResource = object.getModel().getUnderlyingResource();
+			IResource objectResource = underlyingResource;
 			if (objectResource != null)
 				return resource.getProject().equals(objectResource.getProject());
 		}
-		File file = (File) editor.getEditorInput().getAdapter(File.class);
+		File file = editor.getEditorInput().getAdapter(File.class);
 		if (file != null) {
-			IPath path = new Path(object.getModel().getInstallLocation());
+			IPath path = new Path(installLocation);
 			IPath filePath = null;
 			if (ICoreConstants.MANIFEST_FILENAME.equals(file.getName()))
 				filePath = new Path(file.getParentFile().getParent());