/******************************************************************************* * Copyright (c) 2011, 2016 Google, Inc 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 * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Sergey Prigogin (Google) - initial API and implementation *******************************************************************************/ package org.eclipse.cdt.internal.ui.refactoring.utils; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.cdt.core.dom.ast.IASTCompositeTypeSpecifier; import org.eclipse.cdt.core.dom.ast.IASTDeclaration; import org.eclipse.cdt.core.dom.ast.IASTDeclarator; import org.eclipse.cdt.core.dom.ast.IASTName; import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; import org.eclipse.cdt.core.dom.ast.IBinding; import org.eclipse.cdt.core.dom.ast.cpp.ICPPMember; import org.eclipse.cdt.core.index.IIndex; import org.eclipse.cdt.core.index.IIndexBinding; import org.eclipse.cdt.core.index.IIndexFile; import org.eclipse.cdt.core.index.IIndexName; import org.eclipse.cdt.core.model.CoreModelUtil; import org.eclipse.cdt.core.model.ITranslationUnit; import org.eclipse.cdt.core.parser.util.ArrayUtil; import org.eclipse.cdt.internal.core.dom.parser.ASTQueries; import org.eclipse.cdt.internal.ui.editor.ITranslationUnitEditorInput; import org.eclipse.cdt.internal.ui.refactoring.CRefactoringContext; import org.eclipse.cdt.internal.ui.util.EditorUtility; /** * Helper class for finding definitions and class member declarations */ public class DefinitionFinder { /** * Finds the definition for the given name. The definition and the original name may belong * to a different ASTs. The search is done in the index and in the ASTs of dirty editors. * * @param name the name to find the definition for * @param context the refactoring context * @param pm the progress monitor * @return the definition name, or {@code null} if there is no definition or if it is * not unique. * @throws CoreException thrown in case of errors */ public static IASTName getDefinition(IASTName name, CRefactoringContext context, IProgressMonitor pm) throws CoreException, OperationCanceledException { IBinding binding = name.resolveBinding(); if (binding == null) { return null; } return getDefinition(binding, name.getTranslationUnit(), context, pm); } /** * Finds the definition for the given binding. The search is done in the index and in the ASTs * of dirty editors. * * @param binding the binding to find the definition for * @param contextTu the translation unit that determines the set of files to search for * the definition. Only the files directly or indirectly included by the translation unit * are considered. * @param context the refactoring context * @param pm the progress monitor * @return the definition name, or {@code null} if there is no definition or if it is * not unique. * @throws CoreException thrown in case of errors */ public static IASTName getDefinition(IBinding binding, IASTTranslationUnit contextTu, CRefactoringContext context, IProgressMonitor pm) throws CoreException { SubMonitor sm = SubMonitor.convert(pm, 10); IIndex index = context.getIndex(); if (index == null) { return null; } IIndexBinding indexBinding = index.adaptBinding(binding); if (binding == null) return null; Set<String> searchedFiles = new HashSet<String>(); List<IASTName> definitions = new ArrayList<IASTName>(); IIndexName[] definitionsFromIndex = index.findNames(indexBinding, IIndex.FIND_DEFINITIONS | IIndex.SEARCH_ACROSS_LANGUAGE_BOUNDARIES); int remainingCount = definitionsFromIndex.length; SubMonitor loopProgress = sm.newChild(6).setWorkRemaining(remainingCount); for (IIndexName name : definitionsFromIndex) { if (sm.isCanceled()) { throw new OperationCanceledException(); } IIndexFile indexFile = name.getFile(); if (contextTu.getASTFileSet().contains(indexFile) || contextTu.getIndexFileSet().contains(indexFile)) { ITranslationUnit tu = CoreModelUtil.findTranslationUnitForLocation( indexFile.getLocation(), null); if (searchedFiles.add(tu.getLocation().toOSString())) { findDefinitionsInTranslationUnit(indexBinding, tu, context, definitions, pm); if (definitions.size() > 1) return null; } } loopProgress.setWorkRemaining(--remainingCount); } if (definitions.isEmpty()) { // Check dirty editors in case definition has just been introduced but not saved yet. IEditorPart[] dirtyEditors = EditorUtility.getDirtyEditors(true); loopProgress = sm.newChild(3).setWorkRemaining(dirtyEditors.length); for (IEditorPart editor : dirtyEditors) { if (sm.isCanceled()) { throw new OperationCanceledException(); } IEditorInput editorInput = editor.getEditorInput(); if (editorInput instanceof ITranslationUnitEditorInput) { ITranslationUnit tu = ((ITranslationUnitEditorInput) editorInput).getTranslationUnit(); if (searchedFiles.add(tu.getLocation().toOSString())) { findDefinitionsInTranslationUnit(indexBinding, tu, context, definitions, loopProgress.newChild(1)); if (definitions.size() > 1) return null; } } } } return definitions.size() == 1 ? definitions.get(0) : null; } /** * Checks if the given binding has a definition. The search is done in the index and in the ASTs * of dirty editors. * * @param binding the binding to find the definition for * @param context the refactoring context * @param pm the progress monitor * @return <code>true</code> if the binding has a definition. * @throws CoreException thrown in case of errors */ public static boolean hasDefinition(IBinding binding, CRefactoringContext context, IProgressMonitor pm) throws CoreException, OperationCanceledException { SubMonitor sm = SubMonitor.convert(pm, 10); IIndex index = context.getIndex(); if (index == null) { return false; } IIndexBinding indexBinding = index.adaptBinding(binding); if (binding == null) return false; Set<String> dirtyFiles = new HashSet<String>(); IEditorPart[] dirtyEditors = EditorUtility.getDirtyEditors(true); for (IEditorPart editor : dirtyEditors) { IEditorInput editorInput = editor.getEditorInput(); if (editorInput instanceof ITranslationUnitEditorInput) { ITranslationUnit tu = ((ITranslationUnitEditorInput) editorInput).getTranslationUnit(); dirtyFiles.add(tu.getLocation().toOSString()); } } Set<String> searchedFiles = new HashSet<String>(); IIndexName[] definitionsFromIndex = index.findNames(indexBinding, IIndex.FIND_DEFINITIONS | IIndex.SEARCH_ACROSS_LANGUAGE_BOUNDARIES); int remainingCount = definitionsFromIndex.length; SubMonitor loopProgress = sm.newChild(6).setWorkRemaining(remainingCount); for (IIndexName name : definitionsFromIndex) { if (sm.isCanceled()) { throw new OperationCanceledException(); } ITranslationUnit tu = CoreModelUtil.findTranslationUnitForLocation( name.getFile().getLocation(), null); String filename = tu.getLocation().toOSString(); if (searchedFiles.add(filename) && (!dirtyFiles.contains(filename) || hasDefinitionsInTranslationUnit(indexBinding, tu, context, loopProgress.newChild(1)))) { return true; } loopProgress.setWorkRemaining(--remainingCount); } // Check dirty editors in case definition has just been introduced but not saved yet. loopProgress = sm.newChild(3).setWorkRemaining(dirtyEditors.length); for (IEditorPart editor : dirtyEditors) { if (sm.isCanceled()) { throw new OperationCanceledException(); } IEditorInput editorInput = editor.getEditorInput(); if (editorInput instanceof ITranslationUnitEditorInput) { ITranslationUnit tu = ((ITranslationUnitEditorInput) editorInput).getTranslationUnit(); String filename = tu.getLocation().toOSString(); if (searchedFiles.add(filename) && hasDefinitionsInTranslationUnit(indexBinding, tu, context, loopProgress.newChild(1))) { return true; } } } return false; } private static void findDefinitionsInTranslationUnit(IIndexBinding binding, ITranslationUnit tu, CRefactoringContext context, List<IASTName> definitions, IProgressMonitor pm) throws CoreException, OperationCanceledException { IASTTranslationUnit ast = context.getAST(tu, pm); ArrayUtil.addAll(definitions, ast.getDefinitionsInAST(binding)); } private static boolean hasDefinitionsInTranslationUnit(IIndexBinding binding, ITranslationUnit tu, CRefactoringContext context, IProgressMonitor pm) throws CoreException, OperationCanceledException { IASTTranslationUnit ast = context.getAST(tu, pm); return ast.getDefinitionsInAST(binding).length != 0; } /** * Finds the declaration for the given class member. The declaration and the original member * name may belong to a different ASTs. The search is done in the index and in the ASTs of dirty * editors. * * @param memberName the name of the class member to find the declaration for * @param context the refactoring context * @param pm the progress monitor * @return the declaration name, or {@code null} if there is no declaration or if it is * not unique. * @throws CoreException thrown in case of errors */ public static IASTName getMemberDeclaration(IASTName memberName, CRefactoringContext context, IProgressMonitor pm) throws CoreException, OperationCanceledException { IBinding binding = memberName.resolveBinding(); if (!(binding instanceof ICPPMember)) return null; return getMemberDeclaration((ICPPMember) binding, memberName.getTranslationUnit(), context, pm); } /** * Finds the declaration for the given class member. The declaration and the original member * name may belong to a different ASTs. The search is done in the index and in the ASTs of dirty * editors. * * @param member the class member binding to find the declaration for * @param contextTu the translation unit that determines the set of files to search for * the declaration. Only the files directly or indirectly included by the translation unit * are considered. * @param context the refactoring context * @param pm the progress monitor * @return the declaration name, or {@code null} if there is no declaration or if it is * not unique. * @throws CoreException thrown in case of errors */ public static IASTName getMemberDeclaration(ICPPMember member, IASTTranslationUnit contextTu, CRefactoringContext context, IProgressMonitor pm) throws CoreException, OperationCanceledException { IASTName classDefintionName = getDefinition(member.getClassOwner(), contextTu, context, pm); if (classDefintionName == null) return null; IASTCompositeTypeSpecifier compositeTypeSpecifier = ASTQueries.findAncestorWithType(classDefintionName, IASTCompositeTypeSpecifier.class); IASTTranslationUnit ast = classDefintionName.getTranslationUnit(); IIndex index = context.getIndex(); if (index == null) { return null; } IASTName[] memberDeclarationNames = ast.getDeclarationsInAST(index.adaptBinding(member)); for (IASTName name : memberDeclarationNames) { if (name.getPropertyInParent() == IASTDeclarator.DECLARATOR_NAME) { IASTDeclaration declaration = ASTQueries.findAncestorWithType(name, IASTDeclaration.class); if (declaration.getParent() == compositeTypeSpecifier) { return name; } } } return null; } }