/* * Copyright (c) 2013, the Dart project authors. * * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.google.dart.tools.internal.search.ui; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.dart.engine.ast.AstNode; import com.google.dart.engine.ast.SimpleIdentifier; import com.google.dart.engine.element.ClassElement; import com.google.dart.engine.element.ClassMemberElement; import com.google.dart.engine.element.Element; import com.google.dart.engine.element.ElementKind; import com.google.dart.engine.element.FieldElement; import com.google.dart.engine.element.ImportElement; import com.google.dart.engine.element.MethodElement; import com.google.dart.engine.element.PropertyAccessorElement; import com.google.dart.engine.search.MatchKind; import com.google.dart.engine.search.SearchEngine; import com.google.dart.engine.search.SearchFilter; import com.google.dart.engine.search.SearchMatch; import com.google.dart.engine.services.util.HierarchyUtils; import com.google.dart.engine.utilities.source.SourceRange; import com.google.dart.engine.utilities.source.SourceRangeFactory; import com.google.dart.tools.core.DartCore; import com.google.dart.tools.internal.corext.refactoring.util.DartElementUtil; import com.google.dart.tools.ui.DartToolsPlugin; import com.google.dart.tools.ui.actions.AbstractDartSelectionAction_OLD; import com.google.dart.tools.ui.instrumentation.UIInstrumentationBuilder; import com.google.dart.tools.ui.internal.actions.ActionUtil; import com.google.dart.tools.ui.internal.search.SearchMessages; import com.google.dart.tools.ui.internal.text.DartHelpContextIds; import com.google.dart.tools.ui.internal.text.editor.DartEditor; import com.google.dart.tools.ui.internal.text.editor.DartSelection; import com.google.dart.tools.ui.internal.util.ExceptionHandler; import static com.google.dart.tools.internal.search.ui.FindDeclarationsAction.isInvocationNameOrPropertyAccessSelected; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.swt.widgets.Event; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IFileEditorInput; import org.eclipse.ui.IWorkbenchSite; import org.eclipse.ui.PlatformUI; import java.util.List; import java.util.Set; /** * Finds references of the selected {@link Element} in the workspace. * * @coverage dart.editor.ui.search */ public class FindReferencesAction extends AbstractDartSelectionAction_OLD { /** * Shows "Search" view with references to non-local elements with given name. */ public static void searchNameUses(final String name) { try { SearchView view = (SearchView) DartToolsPlugin.showView(SearchView.ID); view.showPage(new SearchMatchPage(view, "Searching for references...") { @Override protected boolean canUseFilterPotential() { return false; } @Override protected IProject getCurrentProject() { return findCurrentProject(); } @Override protected String getQueryElementName() { return name; } @Override protected String getQueryKindName() { return "references"; } @Override protected List<SearchMatch> runQuery() { SearchEngine searchEngine = DartCore.getProjectManager().newSearchEngine(); List<SearchMatch> refs = searchEngine.searchQualifiedMemberReferences(name, null, null); return FindDeclarationsAction.getUniqueMatches(refs); } }); } catch (Throwable e) { ExceptionHandler.handle(e, "Find references", "Exception during search."); } } /** * Finds the "current" project. That is the project of the active editor. */ static IProject findCurrentProject() { IEditorPart editor = DartToolsPlugin.getActiveEditor(); if (editor != null) { IEditorInput input = editor.getEditorInput(); if (input instanceof IFileEditorInput) { IFileEditorInput fileInput = (IFileEditorInput) input; IFile file = fileInput.getFile(); if (file != null) { return file.getProject(); } } } return null; } /** * @return {@code true} if given {@link DartSelection} looks valid. */ private static boolean isValidSelection(DartSelection selection) { Element element = ActionUtil.getActionElement(selection); // unresolved if (element == null && isInvocationNameOrPropertyAccessSelected(selection)) { return true; } // interesting elements AstNode node = getSelectionNode(selection); return isInterestingElement(node, element); } public FindReferencesAction(DartEditor editor) { super(editor); } public FindReferencesAction(IWorkbenchSite site) { super(site); } @Override public void selectionChanged(DartSelection selection) { setEnabled(isValidSelection(selection)); } @Override public void selectionChanged(IStructuredSelection selection) { Element element = getSelectionElement(selection); setEnabled(element != null); } @Override protected void doRun(DartSelection selection, Event event, UIInstrumentationBuilder instrumentation) { Element element = ActionUtil.getActionElement(selection); AstNode node = getSelectionNode(selection); doSearch(element, node); } @Override protected void doRun(IStructuredSelection selection, Event event, UIInstrumentationBuilder instrumentation) { Element element = getSelectionElement(selection); doSearch(element, null); } @Override protected void init() { setText(SearchMessages.Search_FindReferencesAction_label); setToolTipText(SearchMessages.Search_FindReferencesAction_tooltip); PlatformUI.getWorkbench().getHelpSystem().setHelp( this, DartHelpContextIds.FIND_REFERENCES_IN_WORKSPACE_ACTION); } /** * Asks {@link SearchView} to execute query and display results. */ private void doSearch(Element element, AstNode node) { // tweak element = DartElementUtil.getVariableIfSyntheticAccessor(element); if (element instanceof ImportElement) { element = ((ImportElement) element).getImportedLibrary(); } // prepare name String name = null; if (node instanceof SimpleIdentifier) { name = ((SimpleIdentifier) node).getName(); } // show search results try { final SearchEngine searchEngine = DartCore.getProjectManager().newSearchEngine(); final Element searchElement = element; final String searchName = name; SearchView view = (SearchView) DartToolsPlugin.showView(SearchView.ID); if (view == null) { return; } view.showPage(new SearchMatchPage(view, "Searching for references...") { @Override protected void beforeRefresh() { super.beforeRefresh(); } @Override protected boolean canUseFilterPotential() { return searchElement != null; } @Override protected IProject getCurrentProject() { return findCurrentProject(); } @Override protected String getQueryElementName() { // no element if (searchElement == null) { return searchName; } // constructor if (searchElement.getKind() == ElementKind.CONSTRUCTOR) { String className = searchElement.getEnclosingElement().getDisplayName(); String constructorName = searchElement.getDisplayName(); if (StringUtils.isEmpty(constructorName)) { return "constructor " + className + "()"; } else { return "constructor " + className + "." + constructorName + "()"; } } // some other element return searchElement.getDisplayName(); } @Override protected String getQueryKindName() { return "references"; } @Override protected List<SearchMatch> runQuery() { List<SearchMatch> allMatches = Lists.newArrayList(); if (searchElement != null) { allMatches.addAll(findVariableElementDeclaration()); allMatches.addAll(findElementReferences(searchEngine, searchElement)); } addUniqueNameReferences(allMatches, findNameReferences()); allMatches = HierarchyUtils.getAccessibleMatches(searchElement, allMatches); allMatches = FindDeclarationsAction.getUniqueMatches(allMatches); return allMatches; } /** * Adds given "name" references only if there are no "exact" reference with same location. */ private void addUniqueNameReferences(List<SearchMatch> result, List<SearchMatch> nameMatches) { // remember existing locations Set<Pair<Element, SourceRange>> existingRefs = Sets.newHashSet(); for (SearchMatch match : result) { existingRefs.add(ImmutablePair.of(match.getElement(), match.getSourceRange())); } // add new name references for (SearchMatch match : nameMatches) { if (existingRefs.contains(ImmutablePair.of(match.getElement(), match.getSourceRange()))) { continue; } result.add(match); } } private List<SearchMatch> findNameReferences() { if (searchElement != null) { // only class members may have potential references if (!(searchElement.getEnclosingElement() instanceof ClassElement)) { return ImmutableList.of(); } // check kind ElementKind elementKind = searchElement.getKind(); if (elementKind != ElementKind.METHOD && elementKind != ElementKind.FIELD && elementKind != ElementKind.GETTER && elementKind != ElementKind.SETTER) { return ImmutableList.of(); } } // do search return searchEngine.searchQualifiedMemberReferences(searchName, null, new SearchFilter() { @Override public boolean passes(SearchMatch match) { return match.getKind() == MatchKind.NAME_REFERENCE_UNRESOLVED; } }); } /** * For local variable and parameters it is interesting to see their declaration with * initializer and type. */ private List<SearchMatch> findVariableElementDeclaration() { ElementKind elementKind = searchElement.getKind(); if (elementKind != ElementKind.PARAMETER && elementKind != ElementKind.LOCAL_VARIABLE) { return ImmutableList.of(); } return ImmutableList.of(new SearchMatch( null, MatchKind.VARIABLE_WRITE, searchElement, SourceRangeFactory.rangeElementName(searchElement))); } }); } catch (Throwable e) { ExceptionHandler.handle(e, getText(), "Exception during search."); } } private List<SearchMatch> findElementReferences(SearchEngine searchEngine, Element searchElement) { Element[] refElements; if (searchElement instanceof MethodElement || searchElement instanceof FieldElement) { // field or method ClassMemberElement member = (ClassMemberElement) searchElement; Set<ClassMemberElement> hierarchyMembers = HierarchyUtils.getHierarchyMembers( searchEngine, member); refElements = hierarchyMembers.toArray(new ClassMemberElement[hierarchyMembers.size()]); } else if (searchElement.getEnclosingElement() instanceof ClassElement && searchElement instanceof PropertyAccessorElement) { // class property accessor PropertyAccessorElement accessor = (PropertyAccessorElement) searchElement; ClassMemberElement property = (ClassMemberElement) accessor.getVariable(); Set<ClassMemberElement> hierarchyMembers = HierarchyUtils.getHierarchyMembers( searchEngine, property); Set<PropertyAccessorElement> hierarchyAccessors = Sets.newHashSet(); for (ClassMemberElement hierarchyMember : hierarchyMembers) { if (hierarchyMember instanceof FieldElement) { FieldElement hierarchyField = (FieldElement) hierarchyMember; if (accessor.isGetter()) { hierarchyAccessors.add(hierarchyField.getGetter()); } else if (accessor.isSetter()) { hierarchyAccessors.add(hierarchyField.getSetter()); } } } refElements = hierarchyAccessors.toArray(new PropertyAccessorElement[hierarchyAccessors.size()]); } else { // some other element refElements = new Element[] {searchElement}; } // find references to "refElements" List<SearchMatch> references = Lists.newArrayList(); for (Element refElement : refElements) { references.addAll(searchEngine.searchReferences(refElement, null, new SearchFilter() { @Override public boolean passes(SearchMatch match) { MatchKind kind = match.getKind(); if (kind == MatchKind.CONSTRUCTOR_DECLARATION) { return false; } if (kind == MatchKind.ANGULAR_CLOSING_TAG_REFERENCE) { return false; } return true; } })); } return references; } }