/******************************************************************************* * Copyright (c) 2008, 2016 Institute for Software, HSR Hochschule fuer Technik * Rapperswil, University of applied sciences 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: * Institute for Software - initial API and implementation * Sergey Prigogin (Google) *******************************************************************************/ package org.eclipse.cdt.internal.ui.refactoring.extractfunction; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; 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.Platform; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.core.runtime.preferences.IPreferencesService; import org.eclipse.jface.viewers.ISelection; import org.eclipse.ltk.core.refactoring.RefactoringDescriptor; import org.eclipse.ltk.core.refactoring.RefactoringStatus; import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext; import org.eclipse.osgi.util.NLS; import org.eclipse.text.edits.TextEditGroup; import org.eclipse.cdt.core.dom.ast.ASTVisitor; import org.eclipse.cdt.core.dom.ast.DOMException; import org.eclipse.cdt.core.dom.ast.IASTBinaryExpression; import org.eclipse.cdt.core.dom.ast.IASTComment; import org.eclipse.cdt.core.dom.ast.IASTCompoundStatement; import org.eclipse.cdt.core.dom.ast.IASTDeclSpecifier; import org.eclipse.cdt.core.dom.ast.IASTDeclaration; import org.eclipse.cdt.core.dom.ast.IASTDeclarator; import org.eclipse.cdt.core.dom.ast.IASTEqualsInitializer; import org.eclipse.cdt.core.dom.ast.IASTExpression; import org.eclipse.cdt.core.dom.ast.IASTExpressionStatement; import org.eclipse.cdt.core.dom.ast.IASTFunctionCallExpression; import org.eclipse.cdt.core.dom.ast.IASTFunctionDeclarator; import org.eclipse.cdt.core.dom.ast.IASTFunctionDefinition; import org.eclipse.cdt.core.dom.ast.IASTIdExpression; import org.eclipse.cdt.core.dom.ast.IASTInitializerClause; import org.eclipse.cdt.core.dom.ast.IASTName; import org.eclipse.cdt.core.dom.ast.IASTNamedTypeSpecifier; import org.eclipse.cdt.core.dom.ast.IASTNode; import org.eclipse.cdt.core.dom.ast.IASTNode.CopyStyle; import org.eclipse.cdt.core.dom.ast.IASTParameterDeclaration; import org.eclipse.cdt.core.dom.ast.IASTPointerOperator; import org.eclipse.cdt.core.dom.ast.IASTReturnStatement; import org.eclipse.cdt.core.dom.ast.IASTSimpleDeclaration; import org.eclipse.cdt.core.dom.ast.IASTStandardFunctionDeclarator; import org.eclipse.cdt.core.dom.ast.IASTStatement; import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; import org.eclipse.cdt.core.dom.ast.IASTUnaryExpression; import org.eclipse.cdt.core.dom.ast.IBasicType; import org.eclipse.cdt.core.dom.ast.IBinding; import org.eclipse.cdt.core.dom.ast.IField; import org.eclipse.cdt.core.dom.ast.INodeFactory; import org.eclipse.cdt.core.dom.ast.IParameter; import org.eclipse.cdt.core.dom.ast.IType; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTConversionName; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTDeclSpecifier; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTFunctionDeclarator; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTName; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTNameSpecifier; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTOperatorName; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTQualifiedName; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTemplateDeclaration; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTemplateId; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTemplateParameter; import org.eclipse.cdt.core.dom.ast.cpp.ICPPBinding; import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType; import org.eclipse.cdt.core.dom.ast.cpp.ICPPMethod; import org.eclipse.cdt.core.dom.rewrite.ASTRewrite; import org.eclipse.cdt.core.dom.rewrite.TypeHelper; import org.eclipse.cdt.core.formatter.DefaultCodeFormatterOptions; import org.eclipse.cdt.core.index.IIndex; import org.eclipse.cdt.core.model.ICElement; import org.eclipse.cdt.core.model.ICProject; import org.eclipse.cdt.ui.CUIPlugin; import org.eclipse.cdt.ui.PreferenceConstants; import org.eclipse.cdt.internal.core.dom.parser.ASTQueries; import org.eclipse.cdt.internal.core.dom.parser.c.CASTBinaryExpression; import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTBinaryExpression; import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTCompoundStatement; import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTDeclarationStatement; import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTDeclarator; import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTEqualsInitializer; import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTExpressionStatement; import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTFunctionCallExpression; import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTFunctionDefinition; import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTIdExpression; import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTName; import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTQualifiedName; import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTReturnStatement; import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTSimpleDeclaration; import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTTemplateDeclaration; import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.SemanticUtil; import org.eclipse.cdt.internal.core.dom.rewrite.astwriter.ASTWriterVisitor; import org.eclipse.cdt.internal.ui.refactoring.CRefactoring; import org.eclipse.cdt.internal.ui.refactoring.CRefactoringDescriptor; import org.eclipse.cdt.internal.ui.refactoring.ClassMemberInserter; import org.eclipse.cdt.internal.ui.refactoring.ClassMemberInserter.InsertionInfo; import org.eclipse.cdt.internal.ui.refactoring.Container; import org.eclipse.cdt.internal.ui.refactoring.MethodContext; import org.eclipse.cdt.internal.ui.refactoring.MethodContext.ContextType; import org.eclipse.cdt.internal.ui.refactoring.ModificationCollector; import org.eclipse.cdt.internal.ui.refactoring.NameInformation; import org.eclipse.cdt.internal.ui.refactoring.NameInformation.Indirection; import org.eclipse.cdt.internal.ui.refactoring.NodeContainer; import org.eclipse.cdt.internal.ui.refactoring.utils.ASTHelper; import org.eclipse.cdt.internal.ui.refactoring.utils.CPPASTAllVisitor; import org.eclipse.cdt.internal.ui.refactoring.utils.Checks; import org.eclipse.cdt.internal.ui.refactoring.utils.NodeHelper; import org.eclipse.cdt.internal.ui.refactoring.utils.SelectionHelper; import org.eclipse.cdt.internal.ui.viewsupport.BasicElementLabels; public class ExtractFunctionRefactoring extends CRefactoring { public static final String ID = "org.eclipse.cdt.internal.ui.refactoring.extractfunction.ExtractFunctionRefactoring"; //$NON-NLS-1$ private static final String[] EMPTY_STRING_ARRAY = {}; static final Integer NULL_INTEGER = Integer.valueOf(0); private NodeContainer container; private final ExtractFunctionInformation info; final Map<String, Integer> names; final Container<Integer> namesCounter; final Container<Integer> trailPos; private int returnNumber; HashMap<String, Integer> nameTrail; private FunctionExtractor extractor; private INodeFactory nodeFactory; private final DefaultCodeFormatterOptions formattingOptions; private IIndex index; private IASTTranslationUnit ast; public ExtractFunctionRefactoring(ICElement element, ISelection selection, ICProject project) { super(element, selection, project); info = new ExtractFunctionInformation(); name = Messages.ExtractFunctionRefactoring_ExtractFunction; names = new HashMap<>(); namesCounter = new Container<>(NULL_INTEGER); trailPos = new Container<>(NULL_INTEGER); formattingOptions = new DefaultCodeFormatterOptions(project.getOptions(true)); } @Override public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException, OperationCanceledException { SubMonitor sm = SubMonitor.convert(pm, 10); try { RefactoringStatus status = super.checkInitialConditions(sm.newChild(8)); if (status.hasError()) { return status; } index = getIndex(); ast = getAST(tu, sm.newChild(1)); nodeFactory = ast.getASTNodeFactory(); container = findExtractableNodes(); if (isProgressMonitorCanceled(sm, initStatus)) return initStatus; if (container.isEmpty()) { initStatus.addFatalError(Messages.ExtractFunctionRefactoring_NoStmtSelected); return initStatus; } checkForNonExtractableStatements(container, initStatus); List<NameInformation> returnValueCandidates = container.getReturnValueCandidates(); if (returnValueCandidates.size() > 1) { initStatus.addFatalError(Messages.ExtractFunctionRefactoring_TooManyDeclarations); return initStatus; } else if (returnValueCandidates.size() == 1) { info.setMandatoryReturnVariable(returnValueCandidates.get(0)); } info.setParameters(container.getParameterCandidates()); initStatus.merge(checkParameterAndReturnTypes()); if (initStatus.hasFatalError()) return initStatus; extractor = FunctionExtractor.createFor(container.getNodesToWrite()); if (extractor.canChooseReturnValue() && info.getMandatoryReturnVariable() == null) { chooseReturnVariable(); } IPreferencesService preferences = Platform.getPreferencesService(); final boolean outFirst = preferences.getBoolean(CUIPlugin.PLUGIN_ID, PreferenceConstants.FUNCTION_OUTPUT_PARAMETERS_BEFORE_INPUT, false, PreferenceConstants.getPreferenceScopes(project.getProject())); info.sortParameters(outFirst); boolean isExtractExpression = container.getNodesToWrite().get(0) instanceof IASTExpression; info.setExtractExpression(isExtractExpression); info.setDeclarator(getDeclaration(container.getNodesToWrite().get(0))); MethodContext context = NodeHelper.findMethodContext(container.getNodesToWrite().get(0), refactoringContext, sm.newChild(1)); if (context.getType() == ContextType.METHOD && context.getMethodDeclarationName() == null) { initStatus.addFatalError(Messages.ExtractFunctionRefactoring_no_declaration_of_surrounding_method); return initStatus; } info.setMethodContext(context); return initStatus; } finally { sm.done(); } } private void chooseReturnVariable() { NameInformation candidate = null; for (NameInformation param : info.getParameters()) { if (param.isOutput()) { IASTDeclarator declarator = param.getDeclarator(); IType type = TypeHelper.createType(declarator); type = SemanticUtil.getNestedType(type, SemanticUtil.CVTYPE | SemanticUtil.TDEF); if (type instanceof IBasicType) { if (((IBasicType) type).getKind() == IBasicType.Kind.eBoolean) { param.setReturnValue(true); return; } } if (candidate == null && !TypeHelper.shouldBePassedByReference(type, declarator.getTranslationUnit())) { candidate = param; } } } if (candidate != null) candidate.setReturnValue(true); } private void checkForNonExtractableStatements(NodeContainer container, RefactoringStatus status) { NonExtractableStatementFinder finder = new NonExtractableStatementFinder(); for (IASTNode node : container.getNodesToWrite()) { node.accept(finder); if (finder.containsContinue()) { initStatus.addFatalError(Messages.ExtractFunctionRefactoring_Error_Continue); break; } else if (finder.containsBreak()) { initStatus.addFatalError(Messages.ExtractFunctionRefactoring_Error_Break); break; } } ReturnStatementFinder returnFinder = new ReturnStatementFinder(); for (IASTNode node : container.getNodesToWrite()) { node.accept(returnFinder); if (returnFinder.containsReturn()) { initStatus.addFatalError(Messages.ExtractFunctionRefactoring_Error_Return); break; } } } private ICPPASTFunctionDeclarator getDeclaration(IASTNode node) { while (node != null && !(node instanceof IASTFunctionDefinition)) { node = node.getParent(); } if (node != null) { IASTFunctionDeclarator declarator = ((IASTFunctionDefinition) node).getDeclarator(); if (declarator instanceof ICPPASTFunctionDeclarator) { return (ICPPASTFunctionDeclarator) declarator; } } return null; } @Override public RefactoringStatus checkFinalConditions(IProgressMonitor pm, CheckConditionsContext checkContext) throws CoreException, OperationCanceledException { RefactoringStatus status = new RefactoringStatus(); final IASTName methodName = new CPPASTName(info.getMethodName().toCharArray()); MethodContext context = info.getMethodContext(); if (context.getType() == ContextType.METHOD && !context.isInline()) { IASTDeclaration contextDeclaration = context.getMethodDeclaration(); ICPPASTCompositeTypeSpecifier classDeclaration = (ICPPASTCompositeTypeSpecifier) contextDeclaration.getParent(); IASTSimpleDeclaration methodDeclaration = getDeclaration(methodName); if (isMethodAllreadyDefined(methodDeclaration, classDeclaration, getIndex())) { status.addError(Messages.ExtractFunctionRefactoring_name_in_use); } } return status; } @Override protected void collectModifications(IProgressMonitor pm, ModificationCollector collector) throws CoreException, OperationCanceledException { final IASTName methodName = new CPPASTName(info.getMethodName().toCharArray()); MethodContext context = info.getMethodContext(); // Create declaration in class. if (context.getType() == ContextType.METHOD && !context.isInline()) { createMethodDeclaration(methodName, context, collector); } // Create method definition. IASTNode firstNode = container.getNodesToWrite().get(0); createMethodDefinition(methodName, context, firstNode, collector); createMethodCalls(methodName, context, collector); } private void createMethodCalls(IASTName methodName, MethodContext context, ModificationCollector collector) throws CoreException { String title; if (context.getType() == MethodContext.ContextType.METHOD) { title = Messages.ExtractFunctionRefactoring_CreateMethodCall; } else { title = Messages.ExtractFunctionRefactoring_CreateFunctionCall; } IASTNode methodCall = getMethodCall(methodName); IASTNode firstNodeToWrite = container.getNodesToWrite().get(0); ASTRewrite rewriter = collector.rewriterForTranslationUnit( firstNodeToWrite.getTranslationUnit()); TextEditGroup editGroup = new TextEditGroup(title); if (methodCall instanceof IASTDeclaration) { CPPASTDeclarationStatement declarationStatement = new CPPASTDeclarationStatement((IASTDeclaration) methodCall); methodCall = declarationStatement; } insertCallIntoTree(methodCall, container.getNodesToWrite(), rewriter, editGroup); if (info.isReplaceDuplicates()) { replaceSimilar(collector, methodName); } for (IASTNode node : container.getNodesToWrite()) { if (node != firstNodeToWrite) { rewriter.remove(node, editGroup); } } } private void insertCallIntoTree(IASTNode methodCall, List<IASTNode> list, ASTRewrite rewriter, TextEditGroup editGroup) { IASTNode firstNode = list.get(0); if (list.size() > 1 && firstNode.getParent() instanceof IASTBinaryExpression && firstNode.getParent().getParent() instanceof IASTBinaryExpression) { IASTBinaryExpression parent = (IASTBinaryExpression) firstNode.getParent(); IASTExpression leftSubTree = parent.getOperand1(); int op = parent.getOperator(); IASTBinaryExpression newParentNode = new CPPASTBinaryExpression(); IASTBinaryExpression rootBinExp = getRootBinExp(parent, list); newParentNode.setParent(rootBinExp.getParent()); newParentNode.setOperand1(leftSubTree.copy(CopyStyle.withLocations)); newParentNode.setOperator(op); newParentNode.setOperand2((IASTExpression) methodCall); rewriter.replace(rootBinExp, newParentNode, editGroup); } else { rewriter.replace(firstNode, methodCall, editGroup); } } private IASTBinaryExpression getRootBinExp(IASTBinaryExpression binExp, List<IASTNode> nodeList) { while (binExp.getParent() instanceof IASTBinaryExpression && nodeList.contains(((IASTBinaryExpression) binExp.getParent()).getOperand2())) { binExp = (IASTBinaryExpression) binExp.getParent(); } return binExp; } private void createMethodDefinition(final IASTName methodName, MethodContext context, IASTNode firstExtractedNode, ModificationCollector collector) { IASTFunctionDefinition functionToExtractFrom = ASTQueries.findAncestorWithType(firstExtractedNode, IASTFunctionDefinition.class); if (functionToExtractFrom != null) { String title; if (context.getType() == MethodContext.ContextType.METHOD) { title = Messages.ExtractFunctionRefactoring_CreateMethodDef; } else { title = Messages.ExtractFunctionRefactoring_CreateFunctionDef; } ASTRewrite rewriter = collector.rewriterForTranslationUnit(functionToExtractFrom.getTranslationUnit()); addMethod(methodName, context, rewriter, functionToExtractFrom, new TextEditGroup(title)); } } private void createMethodDeclaration(final IASTName astMethodName, MethodContext context, ModificationCollector collector) { ICPPASTCompositeTypeSpecifier classDeclaration = (ICPPASTCompositeTypeSpecifier) context.getMethodDeclaration().getParent(); IASTSimpleDeclaration methodDeclaration = getDeclaration(collector, astMethodName); ASTRewrite rewrite = ClassMemberInserter.createChange(classDeclaration, info.getVisibility(), methodDeclaration, false, collector); // Names of external bindings may have to be qualified to be used in a header file. if (classDeclaration.getTranslationUnit().isHeaderUnit()) qualifyExternalReferences(methodDeclaration, classDeclaration, rewrite); } private void qualifyExternalReferences(IASTNode node, ICPPASTCompositeTypeSpecifier classDeclaration, final ASTRewrite rewrite) { final ICPPBinding owner = (ICPPBinding) classDeclaration.getName().resolveBinding(); final String[] contextQualifiers; try { contextQualifiers = owner.getQualifiedName(); } catch (DOMException e) { CUIPlugin.log(e); return; } node.accept(new ASTVisitor(true) { @Override public int visit(IASTName name) { qualifyForContext((ICPPASTName) name, contextQualifiers, rewrite); return PROCESS_SKIP; // Do non visit internals of qualified names. } }); } private void qualifyForContext(ICPPASTName name, String[] contextQualifiers, ASTRewrite rewrite) { ICPPASTName originalName = (ICPPASTName) name.getOriginalNode(); IBinding binding = originalName.resolveBinding(); try { if (!(binding instanceof ICPPBinding)) return; // Qualification is not needed. String[] names = ((ICPPBinding) binding).getQualifiedName(); names = removeCommonPrefix(names, contextQualifiers); if (names.length <= 1) return; // Qualification is not needed. ICPPASTQualifiedName qualifiedName; if (name instanceof ICPPASTQualifiedName) { qualifiedName = (ICPPASTQualifiedName) name; if (qualifiedName.getQualifier().length >= names.length - 1) return; // Qualified already. } else { qualifiedName = new CPPASTQualifiedName(name.copy(CopyStyle.withLocations)); } for (int i = 0; i < names.length - qualifiedName.getQualifier().length; i++) { qualifiedName.addNameSpecifier(new CPPASTName(names[i].toCharArray())); } if (!(name instanceof ICPPASTQualifiedName)) rewrite.replace(name, qualifiedName, null); } catch (DOMException e) { CUIPlugin.log(e); return; } } private String[] removeCommonPrefix(String[] array1, String[] array2) { for (int i = 0; i < array1.length && i < array2.length; i++) { if (!array1[i].equals(array2[i])) { if (i == 0) return array1; return Arrays.copyOfRange(array1, i, array1.length); } } return EMPTY_STRING_ARRAY; } private void replaceSimilar(ModificationCollector collector, IASTName methodName) { // Find similar code. final List<IASTNode> nodesToRewriteWithoutComments = getNodesWithoutComments(container.getNodesToWrite()); final List<IASTNode> initTrail = getTrail(nodesToRewriteWithoutComments); IASTTranslationUnit ast = nodesToRewriteWithoutComments.get(0).getTranslationUnit(); ast.accept(new SimilarReplacerVisitor(this, container, collector, initTrail, methodName, nodesToRewriteWithoutComments)); } public int getNumberOfDuplicates() { final List<IASTNode> nodesToRewriteWithoutComments = getNodesWithoutComments(container.getNodesToWrite()); final List<IASTNode> initTrail = getTrail(nodesToRewriteWithoutComments); final int[] count = new int[1]; IASTTranslationUnit ast = nodesToRewriteWithoutComments.get(0).getTranslationUnit(); ast.accept(new SimilarFinderVisitor(this, container, initTrail, nodesToRewriteWithoutComments) { @Override protected void foundSimilar() { ++count[0]; } }); return count[0]; } private List<IASTNode> getNodesWithoutComments(List<IASTNode> nodes) { final List<IASTNode> nodesWithoutComments = new ArrayList<>(nodes.size()); for (IASTNode node : nodes) { if (!(node instanceof IASTComment)) { nodesWithoutComments.add(node); } } return nodesWithoutComments; } private List<IASTNode> getTrail(List<IASTNode> stmts) { final List<IASTNode> trail = new ArrayList<>(); nameTrail = new HashMap<>(); final Container<Integer> trailCounter = new Container<>(NULL_INTEGER); for (IASTNode node : stmts) { node.accept(new CPPASTAllVisitor() { @Override public int visitAll(IASTNode node) { if (node instanceof IASTComment) { // Visit comments, but don't add them to the trail return super.visitAll(node); } else if (node instanceof IASTNamedTypeSpecifier) { // Skip if somewhere is a named type specifier trail.add(node); return PROCESS_SKIP; } else if (node instanceof IASTName) { if (node instanceof ICPPASTConversionName && node instanceof ICPPASTOperatorName && node instanceof ICPPASTTemplateId) { trail.add(node); return super.visitAll(node); } else { // Save name sequence number IASTName name = (IASTName) node; TrailName trailName = new TrailName(name); int actCount = trailCounter.getObject().intValue(); if (nameTrail.containsKey(name.getRawSignature())) { Integer value = nameTrail.get(name.getRawSignature()); actCount = value.intValue(); } else { trailCounter.setObject(Integer.valueOf(++actCount)); nameTrail.put(name.getRawSignature(), trailCounter.getObject()); } trailName.setNameNumber(actCount); if (info.getReturnVariable() != null && info.getReturnVariable().getName().getRawSignature().equals( name.getRawSignature())) { returnNumber = actCount; } trail.add(trailName); return PROCESS_SKIP; } } else { trail.add(node); return super.visitAll(node); } } }); } return trail; } boolean isStatementInTrail(final IASTStatement stmt, final List<IASTNode> trail) { final boolean same[] = { true }; final TrailNodeEqualityChecker equalityChecker = new TrailNodeEqualityChecker(names, namesCounter, index); stmt.accept(new CPPASTAllVisitor() { @Override public int visitAll(IASTNode node) { int pos = trailPos.getObject().intValue(); if (trail.size() <= 0 || pos >= trail.size()) { same[0] = false; return PROCESS_ABORT; } if (node instanceof IASTComment) { // Visit comments, but they are not in the trail return super.visitAll(node); } IASTNode trailNode = trail.get(pos); trailPos.setObject(Integer.valueOf(pos + 1)); if (equalityChecker.isEqual(trailNode, node)) { if (node instanceof ICPPASTQualifiedName || node instanceof IASTNamedTypeSpecifier) { return PROCESS_SKIP; } else { return super.visitAll(node); } } else { same[0] = false; return PROCESS_ABORT; } } }); return same[0]; } private boolean isMethodAllreadyDefined(IASTSimpleDeclaration methodDeclaration, ICPPASTCompositeTypeSpecifier classDeclaration, IIndex index) { TrailNodeEqualityChecker equalityChecker = new TrailNodeEqualityChecker(names, namesCounter, index); IBinding bind = classDeclaration.getName().resolveBinding(); IASTStandardFunctionDeclarator declarator = (IASTStandardFunctionDeclarator) methodDeclaration.getDeclarators()[0]; String name = new String(declarator.getName().toCharArray()); if (bind instanceof ICPPClassType) { ICPPClassType classBind = (ICPPClassType) bind; IField[] fields = classBind.getFields(); for (IField field : fields) { if (field.getName().equals(name)) { return true; } } ICPPMethod[] methods = classBind.getAllDeclaredMethods(); for (ICPPMethod method : methods) { if (!method.takesVarArgs() && name.equals(method.getName())) { IParameter[] parameters = method.getParameters(); if (parameters.length == declarator.getParameters().length) { for (int i = 0; i < parameters.length; i++) { IASTName[] origParameterName = ast.getDeclarationsInAST(parameters[i]); IASTParameterDeclaration origParameter = (IASTParameterDeclaration) origParameterName[0].getParent().getParent(); IASTParameterDeclaration newParameter = declarator.getParameters()[i]; // If not the same break; if (!(equalityChecker.isEqual(origParameter.getDeclSpecifier(), newParameter.getDeclSpecifier()) && ASTHelper.samePointers(origParameter.getDeclarator().getPointerOperators(), newParameter.getDeclarator().getPointerOperators(), equalityChecker))) { break; } if (!(i < (parameters.length - 1))) { return true; } } } } } return false; } return true; } private void addMethod(IASTName methodName, MethodContext context, ASTRewrite rewrite, IASTNode functionToExtractFrom, TextEditGroup group) { ICPPASTQualifiedName qname = new CPPASTQualifiedName((ICPPASTName) methodName); if (context.getType() == ContextType.METHOD) { if (context.getMethodQName() != null) { for (ICPPASTNameSpecifier segment : context.getMethodQName().getQualifier()) { qname.addNameSpecifier(segment.copy()); } } } IASTFunctionDefinition func = new CPPASTFunctionDefinition(); func.setParent(ast); List<IASTPointerOperator> pointerOperators = new ArrayList<>(); IASTDeclSpecifier returnType = getReturnType(pointerOperators); func.setDeclSpecifier(returnType); IASTStandardFunctionDeclarator declarator = extractor.createFunctionDeclarator(qname, info.getDeclarator(), info.getReturnVariable(), container.getNodesToWrite(), info.getParameters(), nodeFactory); for (IASTPointerOperator operator : pointerOperators) { declarator.addPointerOperator(operator); } func.setDeclarator(declarator); IASTCompoundStatement compound = new CPPASTCompoundStatement(); func.setBody(compound); ASTRewrite subRewrite; IASTNode parent = functionToExtractFrom.getParent(); IASTNode nodeToInsert = func; if (parent instanceof ICPPASTTemplateDeclaration) { ICPPASTTemplateDeclaration parentTemplate = (ICPPASTTemplateDeclaration) parent; CPPASTTemplateDeclaration templateDeclaration = new CPPASTTemplateDeclaration(); templateDeclaration.setParent(ast); for (ICPPASTTemplateParameter param : parentTemplate.getTemplateParameters()) { templateDeclaration.addTemplateParameter(param.copy(CopyStyle.withLocations)); } functionToExtractFrom = parentTemplate; templateDeclaration.setDeclaration(func); nodeToInsert = templateDeclaration; parent = parent.getParent(); } InsertionInfo insertion; if (parent instanceof ICPPASTCompositeTypeSpecifier) { // Inserting into a class declaration insertion = ClassMemberInserter.findInsertionPoint((ICPPASTCompositeTypeSpecifier) parent, info.getVisibility(), false); } else { // Inserting into a translation unit or a namespace. // TODO(sprigogin): Use insertBeforeNode instead of functionToExtractFrom when creating InsertionInfo // IASTNode insertBeforeNode = info.getMethodContext().getType() == ContextType.METHOD ? // null : functionToExtractFrom; insertion = new InsertionInfo(parent, functionToExtractFrom); } if (insertion.getPrologue() != null) { rewrite.insertBefore(insertion.getParentNode(), insertion.getInsertBeforeNode(), insertion.getPrologue(), group); } subRewrite = rewrite.insertBefore(insertion.getParentNode(), insertion.getInsertBeforeNode(), nodeToInsert, group); if (insertion.getEpilogue() != null) { rewrite.insertBefore(insertion.getParentNode(), insertion.getInsertBeforeNode(), insertion.getEpilogue(), group); } extractor.constructMethodBody(compound, container.getNodesToWrite(), info.getParameters(), subRewrite, group); // Set return value NameInformation returnVariable = info.getReturnVariable(); if (returnVariable != null) { IASTReturnStatement returnStmt = new CPPASTReturnStatement(); IASTIdExpression expr = new CPPASTIdExpression(); if (returnVariable.getNewName() == null) { expr.setName(newName(returnVariable.getName())); } else { expr.setName(new CPPASTName(returnVariable.getNewName().toCharArray())); } returnStmt.setReturnValue(expr); subRewrite.insertBefore(compound, null, returnStmt, group); } } private IASTName newName(IASTName declaration) { return new CPPASTName(declaration.toCharArray()); } private IASTDeclSpecifier getReturnType(List<IASTPointerOperator> pointerOperators) { IASTNode firstNodeToWrite = container.getNodesToWrite().get(0); NameInformation returnVariable = info.getReturnVariable(); return extractor.determineReturnType(firstNodeToWrite, returnVariable, pointerOperators); } protected IASTNode getMethodCall(IASTName astMethodName, Map<String, Integer> trailNameTable, Map<String, Integer> similarNameTable, NodeContainer myContainer, NodeContainer mySimilarContainer) { IASTExpressionStatement stmt = new CPPASTExpressionStatement(); IASTFunctionCallExpression callExpression = new CPPASTFunctionCallExpression(); IASTIdExpression idExpression = new CPPASTIdExpression(); idExpression.setName(astMethodName); List<IASTInitializerClause> args = new ArrayList<IASTInitializerClause>(); Set<IASTName> declarations = new HashSet<IASTName>(); IASTName retName = null; boolean theRetName = false; for (NameInformation nameInfo : info.getParameters()) { String origName = null; Integer trailSeqNumber = trailNameTable.get(nameInfo.getDeclarationName().getRawSignature()); if (trailSeqNumber != null) { for (Entry<String, Integer> entry : similarNameTable.entrySet()) { if (entry.getValue().equals(trailSeqNumber)) { origName = entry.getKey(); if (info.getReturnVariable() != null && trailSeqNumber.intValue() == returnNumber) { theRetName = true; } } } } else { origName = String.valueOf(nameInfo.getDeclarationName().getSimpleID()); } if (origName != null) { boolean found = false; for (NameInformation simNameInfo : mySimilarContainer.getNames()) { if (origName.equals(simNameInfo.getDeclarationName().getRawSignature())) { addParameterIfPossible(args, declarations, simNameInfo); found = true; if (theRetName) { theRetName = false; retName = new CPPASTName( simNameInfo.getDeclarationName().getRawSignature().toCharArray()); } } } if (!found) { // should be a field, use the old name IASTIdExpression expression = new CPPASTIdExpression(); CPPASTName fieldName = new CPPASTName(origName.toCharArray()); expression.setName(fieldName); args.add(expression); if (theRetName) { theRetName = false; retName = fieldName; } } } } callExpression.setArguments(args.toArray(new IASTInitializerClause[args.size()])); callExpression.setFunctionNameExpression(idExpression); if (info.getReturnVariable() == null) { return getReturnAssignment(stmt, callExpression); } return getReturnAssignment(stmt, callExpression, retName); } private IASTNode getMethodCall(IASTName methodName) { IASTExpressionStatement statement = new CPPASTExpressionStatement(); IASTFunctionCallExpression callExpression = new CPPASTFunctionCallExpression(); IASTIdExpression idExpression = new CPPASTIdExpression(); idExpression.setName(new CPPASTName(methodName.toCharArray())); List<IASTInitializerClause> args = getCallParameters(); callExpression.setArguments(args.toArray(new IASTInitializerClause[args.size()])); callExpression.setFunctionNameExpression(idExpression); if (info.getReturnVariable() == null) { return getReturnAssignment(statement, callExpression); } IASTName retName = newName(info.getReturnVariable().getName()); return getReturnAssignment(statement, callExpression, retName); } private IASTNode getReturnAssignment(IASTExpressionStatement stmt, IASTFunctionCallExpression callExpression, IASTName retname) { if (info.getReturnVariable().equals(info.getMandatoryReturnVariable())) { IASTSimpleDeclaration orgDecl = ASTQueries.findAncestorWithType(info.getReturnVariable().getDeclarationName(), IASTSimpleDeclaration.class); IASTSimpleDeclaration decl = new CPPASTSimpleDeclaration(); decl.setDeclSpecifier(orgDecl.getDeclSpecifier().copy(CopyStyle.withLocations)); IASTDeclarator declarator = new CPPASTDeclarator(); declarator.setName(retname); for (IASTPointerOperator pointer : orgDecl.getDeclarators()[0].getPointerOperators()) { declarator.addPointerOperator(pointer.copy(CopyStyle.withLocations)); } IASTEqualsInitializer initializer = new CPPASTEqualsInitializer(); initializer.setInitializerClause(callExpression); declarator.setInitializer(initializer); decl.addDeclarator(declarator); return decl; } IASTBinaryExpression binaryExpression = new CASTBinaryExpression(); binaryExpression.setOperator(IASTBinaryExpression.op_assign); IASTIdExpression nameExpression = new CPPASTIdExpression(); nameExpression.setName(retname); binaryExpression.setOperand1(nameExpression); binaryExpression.setOperand2(callExpression); return getReturnAssignment(stmt, binaryExpression); } private IASTNode getReturnAssignment(IASTExpressionStatement stmt, IASTExpression callExpression) { IASTNode node = container.getNodesToWrite().get(0); return extractor.createReturnAssignment(node, stmt, callExpression); } private IASTSimpleDeclaration getDeclaration(IASTName name) { IASTSimpleDeclaration simpleDecl = new CPPASTSimpleDeclaration(); IASTStandardFunctionDeclarator declarator = extractor.createFunctionDeclarator(name, info.getDeclarator(), info.getReturnVariable(), container.getNodesToWrite(), info.getParameters(), nodeFactory); simpleDecl.addDeclarator(declarator); return simpleDecl; } private IASTSimpleDeclaration getDeclaration(ModificationCollector collector, IASTName name) { List<IASTPointerOperator> pointerOperators = new ArrayList<>(); IASTDeclSpecifier declSpec = getReturnType(pointerOperators); IASTSimpleDeclaration simpleDecl = nodeFactory.newSimpleDeclaration(declSpec); if (info.isVirtual() && declSpec instanceof ICPPASTDeclSpecifier) { ((ICPPASTDeclSpecifier) declSpec).setVirtual(true); } simpleDecl.setParent(ast); IASTStandardFunctionDeclarator declarator = extractor.createFunctionDeclarator(name, info.getDeclarator(), info.getReturnVariable(), container.getNodesToWrite(), info.getParameters(), nodeFactory); for (IASTPointerOperator operator : pointerOperators) { declarator.addPointerOperator(operator); } simpleDecl.addDeclarator(declarator); return simpleDecl; } private NodeContainer findExtractableNodes() { final NodeContainer container = new NodeContainer(); ast.accept(new ASTVisitor() { { shouldVisitStatements = true; shouldVisitExpressions = true; } @Override public int visit(IASTStatement stmt) { if (isNodeInsideSelection(stmt)) { container.add(stmt); return PROCESS_SKIP; } return super.visit(stmt); } @Override public int visit(IASTExpression expression) { if (isNodeInsideSelection(expression)) { container.add(expression); return PROCESS_SKIP; } return super.visit(expression); } }); return container; } private boolean isNodeInsideSelection(IASTNode node) { return node.isPartOfTranslationUnitFile() && SelectionHelper.isNodeInsideRegion(node, selectedRegion); } public List<IASTInitializerClause> getCallParameters() { List<IASTInitializerClause> args = new ArrayList<IASTInitializerClause>(); Set<IASTName> declarations = new HashSet<IASTName>(); for (NameInformation nameInfo : info.getParameters()) { addParameterIfPossible(args, declarations, nameInfo); } return args; } private void addParameterIfPossible(List<IASTInitializerClause> args, Set<IASTName> declarations, NameInformation nameInfo) { if (!container.isDeclaredInSelection(nameInfo)) { IASTName declaration = nameInfo.getDeclarationName(); if (declarations.add(declaration)) { IASTExpression expression = nodeFactory.newIdExpression(newName(declaration)); if (nameInfo.getIndirection() == Indirection.POINTER) { expression = nodeFactory.newUnaryExpression(IASTUnaryExpression.op_amper, expression); } args.add(expression); } } } @Override protected RefactoringDescriptor getRefactoringDescriptor() { Map<String, String> arguments = getArgumentMap(); RefactoringDescriptor desc = new ExtractFunctionRefactoringDescriptor(project.getProject().getName(), "Extract Method Refactoring", "Create method " + info.getMethodName(), arguments); //$NON-NLS-1$//$NON-NLS-2$ return desc; } private Map<String, String> getArgumentMap() { Map<String, String> arguments = new HashMap<String, String>(); arguments.put(CRefactoringDescriptor.FILE_NAME, tu.getLocationURI().toString()); arguments.put(CRefactoringDescriptor.SELECTION, selectedRegion.getOffset() + "," + selectedRegion.getLength()); //$NON-NLS-1$ arguments.put(ExtractFunctionRefactoringDescriptor.NAME, info.getMethodName()); arguments.put(ExtractFunctionRefactoringDescriptor.VISIBILITY, info.getVisibility().toString()); arguments.put(ExtractFunctionRefactoringDescriptor.REPLACE_DUPLICATES, Boolean.toString(info.isReplaceDuplicates())); return arguments; } public ExtractFunctionInformation getRefactoringInfo() { return info; } /** * Checks if the new method name is a valid method name. This method doesn't * check if a method with the same name already exists in the hierarchy. * @return validation status */ public RefactoringStatus checkMethodName() { return Checks.checkIdentifier(info.getMethodName(), tu); } /** * Checks if the parameter names are valid. * @return validation status */ public RefactoringStatus checkParameterNames() { RefactoringStatus result= new RefactoringStatus(); List<NameInformation> parameters = info.getParameters(); Set<String> usedNames = new HashSet<String>(); Set<IASTName> declarations = new HashSet<IASTName>(); for (NameInformation nameInfo : container.getNames()) { IASTName declaration = nameInfo.getDeclarationName(); if (declarations.add(declaration) && !parameters.contains(nameInfo)) { usedNames.add(String.valueOf(nameInfo.getName().getSimpleID())); } } for (NameInformation parameter : parameters) { result.merge(Checks.checkIdentifier(parameter.getNewName(), tu)); for (NameInformation other : parameters) { if (parameter != other && other.getNewName().equals(parameter.getNewName())) { result.addError(NLS.bind(Messages.ExtractFunctionRefactoring_duplicate_parameter, BasicElementLabels.getCElementName(other.getNewName()))); return result; } } if (parameter.isRenamed() && usedNames.contains(parameter.getNewName())) { result.addError(NLS.bind(Messages.ExtractFunctionRefactoring_parameter_name_in_use, BasicElementLabels.getCElementName(parameter.getNewName()))); return result; } } return result; } /** * Checks if the parameter names are valid. * @return validation status */ public RefactoringStatus checkParameterAndReturnTypes() { RefactoringStatus result= new RefactoringStatus(); for (NameInformation parameter : info.getParameters()) { String typeName = parameter.getTypeName(); if (typeName == null || typeName.isEmpty()) { result.addError(NLS.bind(Messages.ExtractFunctionRefactoring_invalid_type, BasicElementLabels.getCElementName(parameter.getNewName()))); return result; } } return result; } /** * Returns the signature of the new method. * * @param methodName the method name used for the new method * @return the signature of the extracted method */ public String getSignature(String methodName) { StringBuilder buf = new StringBuilder(); NameInformation returnVariable = info.getReturnVariable(); if (returnVariable != null) { String type = returnVariable.getReturnType(); if (type != null) { buf.append(type); } else { buf.append("<unknown type>"); //$NON-NLS-1$ } } else { buf.append("void"); //$NON-NLS-1$ } buf.append(' '); buf.append(methodName); if (formattingOptions.insert_space_before_opening_paren_in_method_declaration) buf.append(' '); buf.append('('); List<NameInformation> parameters = info.getParameters(); if (!parameters.isEmpty()) { if (formattingOptions.insert_space_after_opening_paren_in_method_declaration) buf.append(' '); boolean first = true; for (NameInformation parameter : parameters) { if (!first) { if (formattingOptions.insert_space_before_comma_in_method_declaration_parameters) buf.append(' '); buf.append(','); if (formattingOptions.insert_space_after_comma_in_method_declaration_parameters) buf.append(' '); } IASTParameterDeclaration declaration = parameter.getParameterDeclaration(nodeFactory); ASTWriterVisitor writer = new ASTWriterVisitor(); declaration.accept(writer); buf.append(writer.toString()); first = false; } if (formattingOptions.insert_space_before_closing_paren_in_method_declaration) buf.append(' '); } else if (formattingOptions.insert_space_between_empty_parens_in_method_declaration) { buf.append(' '); } buf.append(')'); return buf.toString(); } }