/******************************************************************************* * Copyright (c) 2007, 2010 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.search; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.Region; import org.eclipse.cdt.core.dom.ast.ASTVisitor; import org.eclipse.cdt.core.dom.ast.DOMException; import org.eclipse.cdt.core.dom.ast.IASTComment; import org.eclipse.cdt.core.dom.ast.IASTFileLocation; import org.eclipse.cdt.core.dom.ast.IASTName; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorElseStatement; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorEndifStatement; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorIfStatement; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorIfdefStatement; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorIfndefStatement; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorMacroDefinition; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorStatement; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorUndefStatement; import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; import org.eclipse.cdt.core.dom.ast.IBinding; import org.eclipse.cdt.core.dom.ast.IMacroBinding; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTemplateId; import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType; import org.eclipse.cdt.core.dom.ast.cpp.ICPPConstructor; import org.eclipse.cdt.core.dom.ast.cpp.ICPPMethod; import org.eclipse.cdt.internal.core.dom.parser.ASTTranslationUnit; import org.eclipse.cdt.internal.core.dom.parser.cpp.ClassTypeHelper; /** * Finds locations of linked names. Used by Rename in File and Rename in Workspace. */ public class LinkedNamesFinder { private static final IRegion[] EMPTY_LOCATIONS_ARRAY = new IRegion[0]; private LinkedNamesFinder() { super(); } public static IRegion[] findByName(IASTTranslationUnit root, IASTName name) { IBinding target = name.resolveBinding(); if (target == null) { return EMPTY_LOCATIONS_ARRAY; } BindingFinder bindingFinder = new BindingFinder(root); bindingFinder.find(target); return bindingFinder.getLocations(); } private static class BindingFinder { private final IASTTranslationUnit root; private final List<IRegion> locations; public BindingFinder(IASTTranslationUnit root) { this.root = root; locations = new ArrayList<IRegion>(); } public void find(IBinding target) { if (target instanceof IMacroBinding) { findMacro((IMacroBinding) target); return; } if (target instanceof ICPPConstructor || target instanceof ICPPMethod && ((ICPPMethod) target).isDestructor()) { target = ((ICPPMethod) target).getClassOwner(); } findBinding(target); if (target instanceof ICPPClassType) { ICPPConstructor[] constructors = ((ICPPClassType) target).getConstructors(); for (ICPPConstructor ctor : constructors) { if (!ctor.isImplicit()) { findBinding(ctor); } } ICPPMethod[] methods = ((ICPPClassType) target).getDeclaredMethods(); for (ICPPMethod method : methods) { if (method.isDestructor()) { findBinding(method); } } } else if (target instanceof ICPPMethod) { ICPPMethod method= (ICPPMethod) target; for (ICPPMethod m : ClassTypeHelper.findOverridden(method)) { findBinding(m); } try { for (ICPPMethod m : findOverridersInAST(method)) { findBinding(m); } } catch (DOMException e) { // Ignore. } } } private ICPPMethod[] findOverridersInAST(ICPPMethod method) throws DOMException { if (!ClassTypeHelper.isVirtual(method)) return ICPPMethod.EMPTY_CPPMETHOD_ARRAY; final ICPPClassType ownerClass = method.getClassOwner(); if (ownerClass == null) return ICPPMethod.EMPTY_CPPMETHOD_ARRAY; SubclassFinder subclassFinder = new SubclassFinder(ownerClass); root.accept(subclassFinder); return ClassTypeHelper.findOverriders(subclassFinder.getSubclasses(), method); } public IRegion[] getLocations() { if (locations.isEmpty()) { return EMPTY_LOCATIONS_ARRAY; } return locations.toArray(new IRegion[locations.size()]); } private void findBinding(IBinding target) { IASTName[] names= root.getDeclarationsInAST(target); for (IASTName candidate : names) { if (candidate.isPartOfTranslationUnitFile()) { addLocation(candidate); } } names= root.getReferences(target); for (IASTName candidate : names) { if (candidate.isPartOfTranslationUnitFile()) { addLocation(candidate); } } } private void addLocation(IASTName name) { IBinding binding = name.resolveBinding(); if (binding != null) { if (name instanceof ICPPASTTemplateId) { name= ((ICPPASTTemplateId) name).getTemplateName(); } IASTFileLocation fileLocation= name.getImageLocation(); if (fileLocation == null || !root.getFilePath().equals(fileLocation.getFileName())) { fileLocation= name.getFileLocation(); } if (fileLocation != null) { int offset= fileLocation.getNodeOffset(); int length= fileLocation.getNodeLength(); if (binding instanceof ICPPMethod && ((ICPPMethod) binding).isDestructor()) { // Skip tilde. offset++; length--; } if (offset >= 0 && length > 0) { locations.add(new Region(offset, length)); } } } } /** * Adds all occurrences of a macro name to the list of locations. Macro occurrences * may belong to multiple macro bindings with the same name. Macro names are also * looked for in the comments of #else and #endif statements. * Comments of #else and #endif statements related to #ifdef or #ifndef are searched * for the macro name referenced by the #if[n]def. * @param target a binding representing a macro. */ private void findMacro(IMacroBinding target) { findBinding(target); char[] nameChars = target.getNameCharArray(); List<IASTName> ifdefNameStack = new ArrayList<IASTName>(); IASTPreprocessorStatement[] statements = root.getAllPreprocessorStatements(); for (IASTPreprocessorStatement statement : statements) { if (!statement.isPartOfTranslationUnitFile()) { continue; } IASTName macroName = null; boolean ifStatement = false; if (statement instanceof IASTPreprocessorIfdefStatement) { macroName = ((IASTPreprocessorIfdefStatement) statement).getMacroReference(); ifStatement = true; } else if (statement instanceof IASTPreprocessorIfndefStatement) { macroName = ((IASTPreprocessorIfndefStatement) statement).getMacroReference(); ifStatement = true; } else if (statement instanceof IASTPreprocessorMacroDefinition) { macroName = ((IASTPreprocessorMacroDefinition) statement).getName(); } else if (statement instanceof IASTPreprocessorUndefStatement) { macroName = ((IASTPreprocessorUndefStatement) statement).getMacroName(); } else if (statement instanceof IASTPreprocessorIfStatement) { ifStatement = true; } else if (statement instanceof IASTPreprocessorEndifStatement) { if (!ifdefNameStack.isEmpty()) if (ifdefNameStack.remove(ifdefNameStack.size() - 1) != null) { findInStatementComment(nameChars, statement); } } else if (statement instanceof IASTPreprocessorElseStatement) { if (!ifdefNameStack.isEmpty()) if (ifdefNameStack.get(ifdefNameStack.size() - 1) != null) { findInStatementComment(nameChars, statement); } } if (macroName != null) { if (Arrays.equals(nameChars, macroName.getSimpleID())) { IBinding binding = macroName.resolveBinding(); if (!target.equals(binding)) { findBinding(binding); } } else { macroName = null; } } if (ifStatement) { ifdefNameStack.add(macroName); } } } /** * Finds locations of a given name in the comment of a preprocessor statement. */ private void findInStatementComment(char[] nameChars, IASTPreprocessorStatement statement) { IASTFileLocation location = statement.getFileLocation(); IASTComment comment = findComment(location.getNodeOffset() + location.getNodeLength()); if (comment != null && comment.getFileLocation().getStartingLineNumber() == location.getStartingLineNumber()) { findInComment(nameChars, comment); } } /** * Returns the first comment after the given offset. * @param startOffset a file offset. * @return a comment or <code>null</code>, if there are no comments after the offset. */ private IASTComment findComment(int startOffset) { IASTComment[] comments = ((ASTTranslationUnit) root).getComments(); int low = 0; int high = comments.length; while (low < high) { int mid = (low + high) / 2; int offset = comments[mid].getFileLocation().getNodeOffset(); if (offset < startOffset) { low = mid + 1; } else { high = mid; if (offset == startOffset) { break; } } } return high < comments.length ? comments[high] : null; } /** * Adds all occurrences of a name in a comment to the list of locations. */ private void findInComment(char[] name, IASTComment comment) { char[] text = comment.getComment(); int j = 0; // First two characters are either /* or // for (int i = 2; i <= text.length - name.length + j; i++) { char c = text[i]; if (!Character.isJavaIdentifierPart(c)) { j = 0; } else if (j >= 0 && j < name.length && name[j] == c) { j++; if (j == name.length && (i + 1 == text.length || !Character.isJavaIdentifierPart(text[i + 1]))) { int offset = comment.getFileLocation().getNodeOffset() + i + 1 - name.length; locations.add(new Region(offset, name.length)); j = 0; } } else { j = -1; } } } } /** * Finds subclasses of the given class referenced by the AST. */ static class SubclassFinder extends ASTVisitor { { shouldVisitNames= true; } private final ICPPClassType baseClass; private Set<ICPPClassType> subclasses = new HashSet<ICPPClassType>(); private Set<IBinding> seenClasses = new HashSet<IBinding>(); SubclassFinder(ICPPClassType baseClass) { this.baseClass = baseClass; } @Override public int visit(IASTName name) { IBinding binding = name.resolveBinding(); if (binding instanceof ICPPClassType) { if (seenClasses.add(binding)) { ICPPClassType candidate = (ICPPClassType) binding; if (ClassTypeHelper.isSubclass(candidate, baseClass)) { subclasses.add(candidate); } } } return PROCESS_CONTINUE; } public ICPPClassType[] getSubclasses() { return subclasses.toArray(new ICPPClassType[subclasses.size()]); } } }