/******************************************************************************* * Copyright (c) 2008, 2011 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.extractconstant; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.core.runtime.preferences.IPreferencesService; import org.eclipse.jface.text.Region; import org.eclipse.jface.viewers.ISelection; import org.eclipse.ltk.core.refactoring.RefactoringDescriptor; import org.eclipse.ltk.core.refactoring.RefactoringStatus; import org.eclipse.text.edits.TextEditGroup; import org.eclipse.cdt.core.dom.ast.ASTNodeFactoryFactory; import org.eclipse.cdt.core.dom.ast.ASTVisitor; 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.IASTFunctionDefinition; import org.eclipse.cdt.core.dom.ast.IASTLiteralExpression; import org.eclipse.cdt.core.dom.ast.IASTMacroExpansionLocation; import org.eclipse.cdt.core.dom.ast.IASTNode; import org.eclipse.cdt.core.dom.ast.IASTSimpleDeclaration; import org.eclipse.cdt.core.dom.ast.IASTUnaryExpression; import org.eclipse.cdt.core.dom.ast.IBinding; import org.eclipse.cdt.core.dom.ast.INodeFactory; 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.ICPPASTNamespaceDefinition; import org.eclipse.cdt.core.dom.ast.cpp.ICPPNodeFactory; import org.eclipse.cdt.core.dom.rewrite.ASTRewrite; import org.eclipse.cdt.core.dom.rewrite.DeclarationGenerator; 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.cpp.CPPASTEqualsInitializer; import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTIdExpression; import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTLiteralExpression; import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTName; import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTSimpleDeclaration; import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPMethod; import org.eclipse.cdt.internal.ui.refactoring.AddDeclarationNodeToClassChange; import org.eclipse.cdt.internal.ui.refactoring.CRefactoring; import org.eclipse.cdt.internal.ui.refactoring.CRefactoringDescription; import org.eclipse.cdt.internal.ui.refactoring.MethodContext; import org.eclipse.cdt.internal.ui.refactoring.ModificationCollector; import org.eclipse.cdt.internal.ui.refactoring.gettersandsetters.GetterSetterNameGenerator; import org.eclipse.cdt.internal.ui.refactoring.utils.NodeHelper; import org.eclipse.cdt.internal.ui.refactoring.utils.SelectionHelper; import org.eclipse.cdt.internal.ui.refactoring.utils.TranslationUnitHelper; import org.eclipse.cdt.internal.ui.util.NameComposer; /** * The central class of the Extract Constant Refactoring. Does all the work like checking pre- and * postconditions and collecting/creating the modifications to the AST. * * @author Mirko Stocker */ public class ExtractConstantRefactoring extends CRefactoring { public static final String ID = "org.eclipse.cdt.ui.refactoring.extractconstant.ExtractConstantRefactoring"; //$NON-NLS-1$ private IASTLiteralExpression target = null; private final ArrayList<IASTExpression> literalsToReplace = new ArrayList<IASTExpression>(); private final ExtractConstantInfo info; public ExtractConstantRefactoring(IFile file, ISelection selection, ExtractConstantInfo info, ICProject proj) { super(file,selection, null, proj); this.info = info; this.project = proj; name = Messages.ExtractConstantRefactoring_ExtractConst; } @Override public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException, OperationCanceledException { SubMonitor sm = SubMonitor.convert(pm, 9); try { lockIndex(); try { RefactoringStatus status = super.checkInitialConditions(sm.newChild(6)); if (status.hasError()) { return status; } Collection<IASTLiteralExpression> literalExpressionCollection = findAllLiterals(); if (literalExpressionCollection.isEmpty()) { initStatus.addFatalError(Messages.ExtractConstantRefactoring_LiteralMustBeSelected); return initStatus; } sm.worked(1); if (isProgressMonitorCanceld(sm, initStatus)) return initStatus; boolean oneMarked = region != null && isOneMarked(literalExpressionCollection, region); if (!oneMarked) { //No or more than one marked if (target == null) { //No Selection found; initStatus.addFatalError(Messages.ExtractConstantRefactoring_NoLiteralSelected); } else { //To many selection found initStatus.addFatalError(Messages.ExtractConstantRefactoring_TooManyLiteralSelected); } return initStatus; } sm.worked(1); if (isProgressMonitorCanceld(sm, initStatus)) return initStatus; findAllNodesForReplacement(literalExpressionCollection); info.addNamesToUsedNames(findAllDeclaredNames()); if (info.getName().length() == 0) { info.setName(getDefaultName(target)); } info.setMContext(NodeHelper.findMethodContext(target, getIndex())); sm.done(); } finally { unlockIndex(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return initStatus; } private String getDefaultName(IASTLiteralExpression literal) { String nameString = literal.toString(); switch (literal.getKind()) { case IASTLiteralExpression.lk_char_constant: case IASTLiteralExpression.lk_string_literal: int beginIndex = 1; if (nameString.startsWith("L")) { //$NON-NLS-1$ beginIndex = 2; } final int len= nameString.length(); if (beginIndex < len && len > 0) { nameString = nameString.substring(beginIndex, len - 1); } break; default: break; } IPreferencesService preferences = Platform.getPreferencesService(); int capitalization = preferences.getInt(CUIPlugin.PLUGIN_ID, PreferenceConstants.NAME_STYLE_CONSTANT_CAPITALIZATION, PreferenceConstants.NAME_STYLE_CAPITALIZATION_UPPER_CASE, null); String wordDelimiter = preferences.getString(CUIPlugin.PLUGIN_ID, PreferenceConstants.NAME_STYLE_CONSTANT_WORD_DELIMITER, "_", null); //$NON-NLS-1$ String prefix = preferences.getString(CUIPlugin.PLUGIN_ID, PreferenceConstants.NAME_STYLE_CONSTANT_PREFIX, "", null); //$NON-NLS-1$ String suffix = preferences.getString(CUIPlugin.PLUGIN_ID, PreferenceConstants.NAME_STYLE_CONSTANT_SUFFIX, "", null); //$NON-NLS-1$ NameComposer composer = new NameComposer(capitalization, wordDelimiter, prefix, suffix); return composer.compose(nameString); } private ArrayList<String> findAllDeclaredNames() { ArrayList<String>names = new ArrayList<String>(); IASTFunctionDefinition funcDef = NodeHelper.findFunctionDefinitionInAncestors(target); ICPPASTCompositeTypeSpecifier comTypeSpec = getCompositeTypeSpecifier(funcDef); if (comTypeSpec != null) { for(IASTDeclaration dec : comTypeSpec.getMembers()) { if (dec instanceof IASTSimpleDeclaration) { IASTSimpleDeclaration simpDec = (IASTSimpleDeclaration) dec; for(IASTDeclarator decor : simpDec.getDeclarators()) { names.add(decor.getName().getRawSignature()); } } } } return names; } private ICPPASTCompositeTypeSpecifier getCompositeTypeSpecifier(IASTFunctionDefinition funcDef) { if (funcDef != null) { IBinding binding = funcDef.getDeclarator().getName().resolveBinding(); if (binding instanceof CPPMethod) { CPPMethod methode = (CPPMethod) binding; IASTNode[] declarations = methode.getDeclarations(); IASTNode decl; if (declarations != null) { decl = declarations[0]; } else { decl = methode.getDefinition(); } IASTNode spec = decl.getParent().getParent(); if (spec instanceof ICPPASTCompositeTypeSpecifier) { ICPPASTCompositeTypeSpecifier compTypeSpec = (ICPPASTCompositeTypeSpecifier) spec; return compTypeSpec; } } } return null; } private void findAllNodesForReplacement(Collection<IASTLiteralExpression> literalExpressionCollection) { if (target.getParent() instanceof IASTUnaryExpression) { IASTUnaryExpression unary = (IASTUnaryExpression) target.getParent(); for (IASTLiteralExpression expression : literalExpressionCollection) { if (target.getKind() == expression.getKind() && target.toString().equals(expression.toString()) && expression.getParent() instanceof IASTUnaryExpression && unary.getOperator() == ((IASTUnaryExpression)expression.getParent()).getOperator()) { literalsToReplace.add(((IASTUnaryExpression)expression.getParent())); } } } else { for (IASTLiteralExpression expression : literalExpressionCollection) { if (target.getKind() == expression.getKind() && target.toString().equals(expression.toString())) { literalsToReplace.add(expression); } } } } private boolean isOneMarked(Collection<IASTLiteralExpression> literalExpressionCollection, Region textSelection) { boolean oneMarked = false; for (IASTLiteralExpression expression : literalExpressionCollection) { boolean isInSameFileSelection = SelectionHelper.isInSameFileSelection(textSelection, expression, file); if (isInSameFileSelection) { if (target == null) { target = expression; oneMarked = true; } else { oneMarked = false; } } } return oneMarked; } private Collection<IASTLiteralExpression> findAllLiterals() { final Collection<IASTLiteralExpression> result = new ArrayList<IASTLiteralExpression>(); ast.accept(new ASTVisitor() { { shouldVisitExpressions = true; } @Override public int visit(IASTExpression expression) { if (expression instanceof IASTLiteralExpression) { if (!(expression.getNodeLocations().length == 1 && expression.getNodeLocations()[0] instanceof IASTMacroExpansionLocation)) { IASTLiteralExpression literal = (IASTLiteralExpression) expression; result.add(literal); } } return super.visit(expression); } }); return result; } @Override protected void collectModifications(IProgressMonitor pm, ModificationCollector collector) throws CoreException, OperationCanceledException{ try { lockIndex(); try { MethodContext context = info.getMContext(); Collection<IASTExpression> locLiteralsToReplace = new ArrayList<IASTExpression>(); if (context.getType() == MethodContext.ContextType.METHOD) { for (IASTExpression expression : literalsToReplace) { MethodContext exprContext = NodeHelper.findMethodContext(expression, getIndex()); if (exprContext.getType() == MethodContext.ContextType.METHOD) { if (context.getMethodQName() != null) { if (MethodContext.isSameClass(exprContext.getMethodQName(), context.getMethodQName())) { locLiteralsToReplace.add(expression); } } else { if (MethodContext.isSameClass(exprContext.getMethodDeclarationName(), context.getMethodDeclarationName())) { locLiteralsToReplace.add(expression); } } } } } else { for (IASTExpression expression : literalsToReplace) { IPath path = new Path(expression.getContainingFilename()); IFile expressionFile = ResourcesPlugin.getWorkspace().getRoot().getFileForLocation(path); //expressionFile may be null if the file is NOT in the workspace if (expressionFile != null && expressionFile.equals(file)) { locLiteralsToReplace.add(expression); } } } // Create all Changes for literals String constName = info.getName(); createLiteralToConstantChanges(constName, locLiteralsToReplace, collector); if (context.getType() == MethodContext.ContextType.METHOD) { ICPPASTCompositeTypeSpecifier classDefinition = (ICPPASTCompositeTypeSpecifier) context.getMethodDeclaration().getParent(); AddDeclarationNodeToClassChange.createChange(classDefinition, info.getVisibility(), getConstNodesClass(constName), true, collector); } else { IASTDeclaration nodes = getConstNodesGlobal(constName); ASTRewrite rewriter = collector.rewriterForTranslationUnit(ast); rewriter.insertBefore(ast, TranslationUnitHelper.getFirstNode(ast), nodes, new TextEditGroup(Messages.ExtractConstantRefactoring_CreateConstant)); } } finally { unlockIndex(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } @Override protected RefactoringDescriptor getRefactoringDescriptor() { Map<String, String> arguments = getArgumentMap(); RefactoringDescriptor desc = new ExtractConstantRefactoringDescription(project.getProject().getName(), "Extract Constant Refactoring", "Create constant for " + target.getRawSignature(), arguments); //$NON-NLS-1$//$NON-NLS-2$ return desc; } private Map<String, String> getArgumentMap() { Map<String, String> arguments = new HashMap<String, String>(); arguments.put(CRefactoringDescription.FILE_NAME, file.getLocationURI().toString()); arguments.put(CRefactoringDescription.SELECTION, region.getOffset() + "," + region.getLength()); //$NON-NLS-1$ arguments.put(ExtractConstantRefactoringDescription.NAME, info.getName()); arguments.put(ExtractConstantRefactoringDescription.VISIBILITY, info.getVisibility().toString()); return arguments; } private void createLiteralToConstantChanges(String constName, Iterable<? extends IASTExpression> literals, ModificationCollector collector) { for (IASTExpression each : literals) { ASTRewrite rewrite = collector.rewriterForTranslationUnit(each.getTranslationUnit()); CPPASTIdExpression idExpression = new CPPASTIdExpression(new CPPASTName(constName.toCharArray())); rewrite.replace(each, idExpression, new TextEditGroup(Messages.ExtractConstantRefactoring_ReplaceLiteral)); } } private IASTSimpleDeclaration getConstNodes(String newName) { ICPPNodeFactory factory = ASTNodeFactoryFactory.getDefaultCPPNodeFactory(); DeclarationGenerator generator = DeclarationGenerator.create(factory); IType type = target.getExpressionType(); IASTDeclSpecifier declSpec = generator.createDeclSpecFromType(type); declSpec.setConst(true); IASTDeclarator declarator = generator.createDeclaratorFromType(type, newName.toCharArray()); IASTSimpleDeclaration simple = new CPPASTSimpleDeclaration(); simple.setDeclSpecifier(declSpec); IASTEqualsInitializer init = new CPPASTEqualsInitializer(); if (target.getParent() instanceof IASTUnaryExpression) { IASTUnaryExpression unary = (IASTUnaryExpression) target.getParent(); init.setInitializerClause(unary); } else { CPPASTLiteralExpression expression = new CPPASTLiteralExpression(target.getKind(), target.getValue()); init.setInitializerClause(expression); } declarator.setInitializer(init); simple.addDeclarator(declarator); return simple; } private IASTDeclaration getConstNodesGlobal(String newName) { IASTSimpleDeclaration simple = getConstNodes(newName); INodeFactory factory= ast.getASTNodeFactory(); if (factory instanceof ICPPNodeFactory) { ICPPASTNamespaceDefinition namespace = ((ICPPNodeFactory) factory).newNamespaceDefinition(new CPPASTName()); namespace.addDeclaration(simple); return namespace; } simple.getDeclSpecifier().setStorageClass(IASTDeclSpecifier.sc_static); return simple; } private IASTDeclaration getConstNodesClass(String newName) { IASTSimpleDeclaration simple = getConstNodes(newName); simple.getDeclSpecifier().setStorageClass(IASTDeclSpecifier.sc_static); return simple; } }