/******************************************************************************* * Copyright (c) 2011, 2015 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: * Martin Schwab & Thomas Kallenberg - initial API and implementation * Sergey Prigogin (Google) * Marc-Andre Laperle (Ericsson) * Thomas Corbat (IFS) ******************************************************************************/ package org.eclipse.cdt.internal.ui.refactoring.togglefunction; import java.util.ArrayList; import java.util.List; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; import org.eclipse.text.edits.TextEditGroup; import org.eclipse.cdt.core.CCProjectNature; import org.eclipse.cdt.core.dom.ast.ASTNodeFactoryFactory; import org.eclipse.cdt.core.dom.ast.ASTVisitor; import org.eclipse.cdt.core.dom.ast.IASTArrayModifier; import org.eclipse.cdt.core.dom.ast.IASTComment; 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.IASTEnumerationSpecifier.IASTEnumerator; import org.eclipse.cdt.core.dom.ast.IASTExpression; import org.eclipse.cdt.core.dom.ast.IASTFunctionDeclarator; import org.eclipse.cdt.core.dom.ast.IASTFunctionDefinition; import org.eclipse.cdt.core.dom.ast.IASTInitializer; import org.eclipse.cdt.core.dom.ast.IASTName; 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.IASTProblem; import org.eclipse.cdt.core.dom.ast.IASTSimpleDeclaration; import org.eclipse.cdt.core.dom.ast.IASTStatement; import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; import org.eclipse.cdt.core.dom.ast.IASTTypeId; import org.eclipse.cdt.core.dom.ast.c.ICASTDesignator; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCapture; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCatchHandler; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier.ICPPASTBaseSpecifier; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTFunctionDefinition; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTFunctionWithTryBlock; 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.ICPPASTNamespaceDefinition; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTQualifiedName; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTemplateParameter; import org.eclipse.cdt.core.dom.ast.cpp.ICPPNodeFactory; import org.eclipse.cdt.core.dom.rewrite.ASTRewrite; import org.eclipse.cdt.core.dom.rewrite.ASTRewrite.CommentPosition; import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTNamespaceDefinition; import org.eclipse.cdt.internal.core.dom.rewrite.ASTLiteralNode; import org.eclipse.cdt.internal.ui.refactoring.Container; import org.eclipse.cdt.internal.ui.refactoring.ModificationCollector; public class ToggleFromInHeaderToImplementationStrategy implements IToggleRefactoringStrategy { private class NamespaceFinderVisitor extends ASTVisitor { private final List<ICPPASTNamespaceDefinition> namespaces; private final Container<IASTNode> result; protected int namespaceIndex = -1; protected int deepestMatch = -1; private NamespaceFinderVisitor(List<ICPPASTNamespaceDefinition> namespaces, Container<IASTNode> result) { this.namespaces = namespaces; this.result = result; shouldVisitNamespaces = true; } @Override public int visit(ICPPASTNamespaceDefinition namespaceDefinition) { namespaceIndex++; String namespaceName = namespaceDefinition.getName().toString(); if (namespaces.size() > namespaceIndex && namespaces.get(namespaceIndex).getName().toString().equals(namespaceName)) { if (namespaceIndex > deepestMatch) { result.setObject(namespaceDefinition); deepestMatch = namespaceIndex; } return PROCESS_CONTINUE; } return PROCESS_SKIP; } @Override public int leave(ICPPASTNamespaceDefinition namespaceDefinition) { namespaceIndex--; return super.leave(namespaceDefinition); } } private IASTTranslationUnit implAst; private ToggleRefactoringContext context; private TextEditGroup infoText; private ASTLiteralNode includeNode; public ToggleFromInHeaderToImplementationStrategy(final ToggleRefactoringContext context) { this.infoText = new TextEditGroup(Messages.EditGroupName); this.context = context; } @Override public void run(ModificationCollector collector) throws CoreException { if (!newFileCheck()) { return; } ICPPASTFunctionDefinition newDefinition = getNewDefinition(); if (context.getDeclaration() != null) { removeDefinitionFromHeader(collector); } else { replaceDefinitionWithDeclaration(collector); } ASTRewrite implRewrite = collector.rewriterForTranslationUnit(implAst); if (includeNode != null) { implRewrite.insertBefore(implAst, null, includeNode, infoText); } IASTNode insertionParent = implAst.getTranslationUnit(); List<ICPPASTNamespaceDefinition> namespaces = getSurroundingNamespaces(); if (!namespaces.isEmpty()) { IASTNode namespaceInImplementation = searchNamespaceInImplementation(namespaces); if (namespaceInImplementation != null) { insertionParent = namespaceInImplementation; } adaptQualifiedNameToNamespaceLevel(newDefinition, namespaces); List<ICPPASTNamespaceDefinition> namespacesToAdd = getNamespacesToAdd(namespaces); for (ICPPASTNamespaceDefinition namespace : namespacesToAdd) { ICPPASTNamespaceDefinition newNamespace = createNamespace(namespace); implRewrite = implRewrite.insertBefore(insertionParent, null, newNamespace, infoText); insertionParent = newNamespace; } } newDefinition.setParent(insertionParent); IASTNode insertionPoint = findInsertionPoint(insertionParent, context.getDeclarationAST()); ASTRewrite newRewriter = implRewrite.insertBefore(insertionParent, insertionPoint, newDefinition, infoText); copyCommentsToNewFile(newDefinition, newRewriter, collector.rewriterForTranslationUnit(context.getDefinitionAST())); restoreLeadingComments(newDefinition, newRewriter, collector); } private void copyCommentsToNewFile(ICPPASTFunctionDefinition newDefinition, final ASTRewrite newRewriter, final ASTRewrite oldRewriter) { newDefinition.accept(new ASTVisitor(true) { @Override public int visit(IASTName name) { copy(name); return super.visit(name); } @Override public int visit(IASTDeclaration declaration) { copy(declaration); return super.visit(declaration); } @Override public int visit(IASTInitializer initializer) { copy(initializer); return super.visit(initializer); } @Override public int visit(IASTParameterDeclaration parameterDeclaration) { copy(parameterDeclaration); return super.visit(parameterDeclaration); } @Override public int visit(IASTDeclarator declarator) { copy(declarator); return super.visit(declarator); } @Override public int visit(IASTDeclSpecifier declSpec) { copy(declSpec); return super.visit(declSpec); } @Override public int visit(IASTArrayModifier arrayModifier) { copy(arrayModifier); return super.visit(arrayModifier); } @Override public int visit(IASTPointerOperator ptrOperator) { copy(ptrOperator); return super.visit(ptrOperator); } @Override public int visit(IASTExpression expression) { copy(expression); return super.visit(expression); } @Override public int visit(IASTStatement statement) { copy(statement); return super.visit(statement); } @Override public int visit(IASTTypeId typeId) { copy(typeId); return super.visit(typeId); } @Override public int visit(IASTEnumerator enumerator) { copy(enumerator); return super.visit(enumerator); } @Override public int visit(IASTProblem problem) { copy(problem); return super.visit(problem); } @Override public int visit(ICPPASTBaseSpecifier baseSpecifier) { copy(baseSpecifier); return super.visit(baseSpecifier); } @Override public int visit(ICPPASTNamespaceDefinition namespaceDefinition) { copy(namespaceDefinition); return super.visit(namespaceDefinition); } @Override public int visit(ICPPASTTemplateParameter templateParameter) { copy(templateParameter); return super.visit(templateParameter); } @Override public int visit(ICPPASTCapture capture) { copy(capture); return super.visit(capture); } @Override public int visit(ICASTDesignator designator) { copy(designator); return super.visit(designator); } private void copy(IASTNode node) { copyComments(node, newRewriter, oldRewriter, CommentPosition.leading); copyComments(node, newRewriter, oldRewriter, CommentPosition.trailing); copyComments(node, newRewriter, oldRewriter, CommentPosition.freestanding); } private void copyComments(IASTNode node, ASTRewrite newRewriter, ASTRewrite oldRewriter, CommentPosition pos) { IASTNode originalNode = node.getOriginalNode(); if (originalNode != node) { List<IASTComment> comments = oldRewriter.getComments(originalNode, pos); for (IASTComment comment : comments) { newRewriter.addComment(node, comment, pos); } } } }); } private boolean newFileCheck() throws CoreException { implAst = context.getASTForPartnerFile(); if (implAst == null) { IProject project = context.getSelectionTU().getCProject().getProject(); boolean isCC = project.hasNature(CCProjectNature.CC_NATURE_ID); ToggleFileCreator fileCreator = new ToggleFileCreator(context, isCC ? ".cpp" : ".c"); //$NON-NLS-1$ //$NON-NLS-2$ if (fileCreator.askUserForFileCreation(context)) { IFile file = fileCreator.createNewFile(); implAst = context.getAST(file, null); includeNode = new ASTLiteralNode(fileCreator.getIncludeStatement()); return true; } else { return false; } } return true; } private List<ICPPASTNamespaceDefinition> getSurroundingNamespaces() { IASTNode toquery = context.getDeclaration(); if (toquery == null) { toquery = context.getDefinition(); } return ToggleNodeHelper.findSurroundingNamespaces(toquery); } private IASTNode findInsertionPoint(IASTNode insertionParent, IASTTranslationUnit unit) { IASTFunctionDeclarator declarator = context.getDeclaration(); if (unit == null) { unit = context.getDefinitionAST(); } if (declarator == null) { declarator = context.getDefinition().getDeclarator(); } IASTNode insertion_point = InsertionPointFinder.findInsertionPoint( unit, insertionParent.getTranslationUnit(), declarator); return insertion_point; } private void restoreLeadingComments(ICPPASTFunctionDefinition newDefinition, ASTRewrite newRewriter, ModificationCollector collector) { ASTRewrite rw = collector.rewriterForTranslationUnit(context.getDefinitionAST()); List<IASTComment>comments = rw.getComments(context.getDefinition(), CommentPosition.leading); if (comments != null) { for (IASTComment comment : comments) { newRewriter.addComment(newDefinition, comment, CommentPosition.leading); if (context.getDeclaration() != null) { rw.remove(comment, infoText); } } } } private void replaceDefinitionWithDeclaration(ModificationCollector collector) { IASTSimpleDeclaration newdeclarator = ToggleNodeHelper.createDeclarationFromDefinition(context.getDefinition()); ASTRewrite rewrite = collector.rewriterForTranslationUnit(context.getDefinitionAST()); rewrite.replace(context.getDefinition(), newdeclarator, infoText); } private ICPPASTFunctionDefinition getNewDefinition() { ICPPASTFunctionDefinition newDefinition = ToggleNodeHelper.createFunctionSignatureWithEmptyBody( context.getDefinition().getDeclSpecifier().copy(CopyStyle.withLocations), context.getDefinition().getDeclarator().copy(CopyStyle.withLocations), context.getDefinition().copy(CopyStyle.withLocations)); newDefinition.getDeclSpecifier().setInline(false); newDefinition.setBody(context.getDefinition().getBody().copy(CopyStyle.withLocations)); if (newDefinition instanceof ICPPASTFunctionWithTryBlock) { ICPPASTFunctionWithTryBlock newTryFun = (ICPPASTFunctionWithTryBlock) newDefinition; ICPPASTFunctionWithTryBlock oldTryFun = (ICPPASTFunctionWithTryBlock) context.getDefinition(); for (ICPPASTCatchHandler catchHandler : oldTryFun.getCatchHandlers()) { newTryFun.addCatchHandler(catchHandler.copy(CopyStyle.withLocations)); } } return newDefinition; } private void adaptQualifiedNameToNamespaceLevel(IASTFunctionDefinition new_definition, List<ICPPASTNamespaceDefinition> namespaces) { if (new_definition.getDeclarator().getName() instanceof ICPPASTQualifiedName && !namespaces.isEmpty()) { ICPPNodeFactory nodeFactory = ASTNodeFactoryFactory.getDefaultCPPNodeFactory(); ICPPASTQualifiedName qname = (ICPPASTQualifiedName) new_definition.getDeclarator().getName(); ICPPASTName lastNameCopy = nodeFactory.newName(qname.getLastName().toCharArray()); ICPPASTQualifiedName qname_new = nodeFactory.newQualifiedName(lastNameCopy); boolean start = false; ICPPASTNameSpecifier[] qualifiers = qname.getQualifier(); for (int i = 0; i < qualifiers.length; i++) { String qualifierName = qualifiers[i].toString(); if (i < namespaces.size() && qualifierName.equals(namespaces.get(i).getName().toString())) { start = true; } else if (start) { qname_new.addNameSpecifier(qualifiers[i]); } } if (start) { new_definition.getDeclarator().setName(qname_new); } } } private CPPASTNamespaceDefinition createNamespace(ICPPASTNamespaceDefinition parent_namespace) { CPPASTNamespaceDefinition insertionParent = new CPPASTNamespaceDefinition( parent_namespace.getName().copy(CopyStyle.withLocations)); insertionParent.setParent(implAst); return insertionParent; } private void removeDefinitionFromHeader(ModificationCollector collector) { ASTRewrite header_rewrite = collector.rewriterForTranslationUnit( context.getDefinitionAST()); header_rewrite.remove(ToggleNodeHelper.getParentRemovePoint(context.getDefinition()), infoText); } private IASTNode searchNamespaceInImplementation(final List<ICPPASTNamespaceDefinition> namespaces) { final Container<IASTNode> result = new Container<IASTNode>(); ASTVisitor visitor = new NamespaceFinderVisitor(namespaces, result); this.implAst.accept(visitor); return result.getObject(); } private List<ICPPASTNamespaceDefinition> getNamespacesToAdd(final List<ICPPASTNamespaceDefinition> namespaces) { final List<ICPPASTNamespaceDefinition> result = new ArrayList<ICPPASTNamespaceDefinition>(); this.implAst.accept(new NamespaceFinderVisitor(namespaces, new Container<IASTNode>()) { { shouldVisitTranslationUnit = true; } @Override public int leave(IASTTranslationUnit tu) { int startIndex = deepestMatch + 1; int namespacesSize = namespaces.size(); if (startIndex < namespacesSize) { result.addAll(namespaces.subList(startIndex, namespacesSize)); } return PROCESS_CONTINUE; } }); return result; } }