| /******************************************************************************* |
| * Copyright (c) 2000, 2019 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Pierre-Yves B. <pyvesdev@gmail.com> - [inline] Allow inlining of local variable initialized to null. - https://bugs.eclipse.org/93850 |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.corext.refactoring.code; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.StringTokenizer; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.SubProgressMonitor; |
| |
| import org.eclipse.text.edits.TextEditGroup; |
| |
| import org.eclipse.ltk.core.refactoring.Change; |
| import org.eclipse.ltk.core.refactoring.Refactoring; |
| import org.eclipse.ltk.core.refactoring.RefactoringChangeDescriptor; |
| import org.eclipse.ltk.core.refactoring.RefactoringDescriptor; |
| import org.eclipse.ltk.core.refactoring.RefactoringStatus; |
| |
| import org.eclipse.jdt.core.ICompilationUnit; |
| import org.eclipse.jdt.core.IJavaElement; |
| import org.eclipse.jdt.core.IJavaProject; |
| import org.eclipse.jdt.core.JavaModelException; |
| import org.eclipse.jdt.core.NamingConventions; |
| import org.eclipse.jdt.core.SourceRange; |
| import org.eclipse.jdt.core.dom.AST; |
| import org.eclipse.jdt.core.dom.ASTNode; |
| import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; |
| import org.eclipse.jdt.core.dom.Annotation; |
| import org.eclipse.jdt.core.dom.BodyDeclaration; |
| import org.eclipse.jdt.core.dom.CompilationUnit; |
| import org.eclipse.jdt.core.dom.EnumDeclaration; |
| import org.eclipse.jdt.core.dom.Expression; |
| import org.eclipse.jdt.core.dom.ExpressionStatement; |
| import org.eclipse.jdt.core.dom.FieldAccess; |
| import org.eclipse.jdt.core.dom.FieldDeclaration; |
| import org.eclipse.jdt.core.dom.IExtendedModifier; |
| import org.eclipse.jdt.core.dom.ITypeBinding; |
| import org.eclipse.jdt.core.dom.Initializer; |
| import org.eclipse.jdt.core.dom.Javadoc; |
| import org.eclipse.jdt.core.dom.MethodDeclaration; |
| import org.eclipse.jdt.core.dom.Modifier; |
| import org.eclipse.jdt.core.dom.Name; |
| import org.eclipse.jdt.core.dom.NullLiteral; |
| import org.eclipse.jdt.core.dom.QualifiedName; |
| import org.eclipse.jdt.core.dom.SimpleName; |
| import org.eclipse.jdt.core.dom.SwitchCase; |
| import org.eclipse.jdt.core.dom.Type; |
| import org.eclipse.jdt.core.dom.VariableDeclarationFragment; |
| import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; |
| import org.eclipse.jdt.core.dom.rewrite.ImportRewrite; |
| import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext; |
| import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.TypeLocation; |
| import org.eclipse.jdt.core.dom.rewrite.ListRewrite; |
| import org.eclipse.jdt.core.manipulation.CodeGeneration; |
| import org.eclipse.jdt.core.refactoring.CompilationUnitChange; |
| import org.eclipse.jdt.core.refactoring.IJavaRefactorings; |
| import org.eclipse.jdt.core.refactoring.descriptors.ExtractConstantDescriptor; |
| import org.eclipse.jdt.core.refactoring.descriptors.JavaRefactoringDescriptor; |
| |
| import org.eclipse.jdt.internal.core.manipulation.StubUtility; |
| import org.eclipse.jdt.internal.core.manipulation.dom.ASTResolving; |
| import org.eclipse.jdt.internal.core.manipulation.util.BasicElementLabels; |
| import org.eclipse.jdt.internal.core.refactoring.descriptors.RefactoringSignatureDescriptorFactory; |
| import org.eclipse.jdt.internal.corext.Corext; |
| import org.eclipse.jdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext; |
| import org.eclipse.jdt.internal.corext.dom.ASTNodes; |
| import org.eclipse.jdt.internal.corext.dom.Bindings; |
| import org.eclipse.jdt.internal.corext.dom.ScopeAnalyzer; |
| import org.eclipse.jdt.internal.corext.dom.fragments.ASTFragmentFactory; |
| import org.eclipse.jdt.internal.corext.dom.fragments.IASTFragment; |
| import org.eclipse.jdt.internal.corext.dom.fragments.IExpressionFragment; |
| import org.eclipse.jdt.internal.corext.fix.LinkedProposalModel; |
| import org.eclipse.jdt.internal.corext.fix.LinkedProposalPositionGroup; |
| import org.eclipse.jdt.internal.corext.refactoring.Checks; |
| import org.eclipse.jdt.internal.corext.refactoring.JDTRefactoringDescriptorComment; |
| import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringArguments; |
| import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringDescriptorUtil; |
| import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages; |
| import org.eclipse.jdt.internal.corext.refactoring.base.RefactoringStatusCodes; |
| import org.eclipse.jdt.internal.corext.refactoring.rename.RefactoringAnalyzeUtil; |
| import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite; |
| import org.eclipse.jdt.internal.corext.refactoring.util.RefactoringASTParser; |
| import org.eclipse.jdt.internal.corext.util.JdtFlags; |
| import org.eclipse.jdt.internal.corext.util.Messages; |
| |
| import org.eclipse.jdt.internal.ui.preferences.JavaPreferencesSettings; |
| import org.eclipse.jdt.internal.ui.text.correction.ModifierCorrectionSubProcessorCore; |
| |
| public class ExtractConstantRefactoring extends Refactoring { |
| |
| private static final String ATTRIBUTE_REPLACE= "replace"; //$NON-NLS-1$ |
| private static final String ATTRIBUTE_QUALIFY= "qualify"; //$NON-NLS-1$ |
| private static final String ATTRIBUTE_VISIBILITY= "visibility"; //$NON-NLS-1$ |
| |
| private static final String MODIFIER= "static final"; //$NON-NLS-1$ |
| |
| private static final String KEY_NAME= "name"; //$NON-NLS-1$ |
| private static final String KEY_TYPE= "type"; //$NON-NLS-1$ |
| |
| private CompilationUnitRewrite fCuRewrite; |
| private int fSelectionStart; |
| private int fSelectionLength; |
| private ICompilationUnit fCu; |
| |
| private IExpressionFragment fSelectedExpression; |
| private Type fConstantTypeCache; |
| private boolean fReplaceAllOccurrences= true; //default value |
| private boolean fQualifyReferencesWithDeclaringClassName= false; //default value |
| |
| private String fVisibility= JdtFlags.VISIBILITY_STRING_PRIVATE; //default value |
| private boolean fTargetIsInterface= false; |
| private String fConstantName; |
| private String[] fExcludedVariableNames; |
| |
| private boolean fSelectionAllStaticFinal; |
| private boolean fAllStaticFinalCheckPerformed= false; |
| |
| //Constant Declaration Location |
| private BodyDeclaration fToInsertAfter; |
| private boolean fInsertFirst; |
| |
| private CompilationUnitChange fChange; |
| private String[] fGuessedConstNames; |
| |
| private LinkedProposalModel fLinkedProposalModel; |
| private boolean fCheckResultForCompileProblems; |
| |
| /** |
| * Creates a new extract constant refactoring |
| * @param unit the compilation unit, or <code>null</code> if invoked by scripting |
| * @param selectionStart start |
| * @param selectionLength length |
| */ |
| public ExtractConstantRefactoring(ICompilationUnit unit, int selectionStart, int selectionLength) { |
| Assert.isTrue(selectionStart >= 0); |
| Assert.isTrue(selectionLength >= 0); |
| fSelectionStart= selectionStart; |
| fSelectionLength= selectionLength; |
| fCu= unit; |
| fCuRewrite= null; |
| fLinkedProposalModel= null; |
| fConstantName= ""; //$NON-NLS-1$ |
| fCheckResultForCompileProblems= true; |
| } |
| |
| public ExtractConstantRefactoring(CompilationUnit astRoot, int selectionStart, int selectionLength) { |
| Assert.isTrue(selectionStart >= 0); |
| Assert.isTrue(selectionLength >= 0); |
| Assert.isTrue(astRoot.getTypeRoot() instanceof ICompilationUnit); |
| |
| fSelectionStart= selectionStart; |
| fSelectionLength= selectionLength; |
| fCu= (ICompilationUnit) astRoot.getTypeRoot(); |
| fCuRewrite= new CompilationUnitRewrite(fCu, astRoot); |
| fLinkedProposalModel= null; |
| fConstantName= ""; //$NON-NLS-1$ |
| fCheckResultForCompileProblems= true; |
| } |
| |
| public ExtractConstantRefactoring(JavaRefactoringArguments arguments, RefactoringStatus status) { |
| this((ICompilationUnit) null, 0, 0); |
| RefactoringStatus initializeStatus= initialize(arguments); |
| status.merge(initializeStatus); |
| } |
| |
| public void setCheckResultForCompileProblems(boolean checkResultForCompileProblems) { |
| fCheckResultForCompileProblems= checkResultForCompileProblems; |
| } |
| |
| public void setLinkedProposalModel(LinkedProposalModel linkedProposalModel) { |
| fLinkedProposalModel= linkedProposalModel; |
| } |
| |
| @Override |
| public String getName() { |
| return RefactoringCoreMessages.ExtractConstantRefactoring_name; |
| } |
| |
| public boolean replaceAllOccurrences() { |
| return fReplaceAllOccurrences; |
| } |
| |
| public void setReplaceAllOccurrences(boolean replaceAllOccurrences) { |
| fReplaceAllOccurrences= replaceAllOccurrences; |
| } |
| |
| public void setVisibility(String am) { |
| Assert.isTrue( |
| am == JdtFlags.VISIBILITY_STRING_PRIVATE || am == JdtFlags.VISIBILITY_STRING_PROTECTED || am == JdtFlags.VISIBILITY_STRING_PACKAGE || am == JdtFlags.VISIBILITY_STRING_PUBLIC |
| ); |
| fVisibility= am; |
| } |
| |
| public String getVisibility() { |
| return fVisibility; |
| } |
| |
| public boolean getTargetIsInterface() { |
| return fTargetIsInterface; |
| } |
| |
| public boolean qualifyReferencesWithDeclaringClassName() { |
| return fQualifyReferencesWithDeclaringClassName; |
| } |
| |
| public void setQualifyReferencesWithDeclaringClassName(boolean qualify) { |
| fQualifyReferencesWithDeclaringClassName= qualify; |
| } |
| |
| public String guessConstantName() { |
| String[] proposals= guessConstantNames(); |
| if (proposals.length > 0) |
| return proposals[0]; |
| else |
| return fConstantName; |
| } |
| |
| /** |
| * @return proposed variable names (may be empty, but not null). |
| * The first proposal should be used as "best guess" (if it exists). |
| */ |
| public String[] guessConstantNames() { |
| if (fGuessedConstNames == null) { |
| try { |
| Expression expression= getSelectedExpression().getAssociatedExpression(); |
| if (expression != null) { |
| ITypeBinding binding= guessBindingForReference(expression); |
| fGuessedConstNames= StubUtility.getVariableNameSuggestions(NamingConventions.VK_STATIC_FINAL_FIELD, fCu.getJavaProject(), binding, expression, Arrays.asList(getExcludedVariableNames())); |
| } |
| } catch (JavaModelException e) { |
| } |
| if (fGuessedConstNames == null) |
| fGuessedConstNames= new String[0]; |
| } |
| return fGuessedConstNames; |
| } |
| |
| |
| private String[] getExcludedVariableNames() { |
| if (fExcludedVariableNames == null) { |
| try { |
| IExpressionFragment expr= getSelectedExpression(); |
| Collection<String> takenNames= new ScopeAnalyzer(fCuRewrite.getRoot()).getUsedVariableNames(expr.getStartPosition(), expr.getLength()); |
| fExcludedVariableNames= takenNames.toArray(new String[takenNames.size()]); |
| } catch (JavaModelException e) { |
| fExcludedVariableNames= new String[0]; |
| } |
| } |
| return fExcludedVariableNames; |
| } |
| |
| @Override |
| public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException { |
| try { |
| pm.beginTask("", 7); //$NON-NLS-1$ |
| |
| RefactoringStatus result= Checks.validateEdit(fCu, getValidationContext()); |
| if (result.hasFatalError()) |
| return result; |
| pm.worked(1); |
| |
| if (fCuRewrite == null) { |
| CompilationUnit cuNode= RefactoringASTParser.parseWithASTProvider(fCu, true, new SubProgressMonitor(pm, 3)); |
| fCuRewrite= new CompilationUnitRewrite(fCu, cuNode); |
| } else { |
| pm.worked(3); |
| } |
| result.merge(checkSelection(new SubProgressMonitor(pm, 3))); |
| |
| if (result.hasFatalError()) |
| return result; |
| |
| if (isLiteralNodeSelected()) |
| fReplaceAllOccurrences= false; |
| |
| if (isInTypeDeclarationAnnotation(getSelectedExpression().getAssociatedNode())) { |
| fVisibility= JdtFlags.VISIBILITY_STRING_PACKAGE; |
| } |
| |
| ITypeBinding targetType= getContainingTypeBinding(); |
| if (targetType.isInterface()) { |
| fTargetIsInterface= true; |
| fVisibility= JdtFlags.VISIBILITY_STRING_PUBLIC; |
| } |
| |
| return result; |
| } finally { |
| pm.done(); |
| } |
| } |
| |
| public boolean selectionAllStaticFinal() { |
| Assert.isTrue(fAllStaticFinalCheckPerformed); |
| return fSelectionAllStaticFinal; |
| } |
| |
| private void checkAllStaticFinal() throws JavaModelException { |
| fSelectionAllStaticFinal= ConstantChecks.isStaticFinalConstant(getSelectedExpression()); |
| fAllStaticFinalCheckPerformed= true; |
| } |
| |
| private RefactoringStatus checkSelection(IProgressMonitor pm) throws JavaModelException { |
| try { |
| pm.beginTask("", 2); //$NON-NLS-1$ |
| |
| IExpressionFragment selectedExpression= getSelectedExpression(); |
| |
| if (selectedExpression == null) { |
| String message= RefactoringCoreMessages.ExtractConstantRefactoring_select_expression; |
| return CodeRefactoringUtil.checkMethodSyntaxErrors(fSelectionStart, fSelectionLength, fCuRewrite.getRoot(), message); |
| } |
| pm.worked(1); |
| |
| RefactoringStatus result= new RefactoringStatus(); |
| result.merge(checkExpression()); |
| if (result.hasFatalError()) |
| return result; |
| pm.worked(1); |
| |
| return result; |
| } finally { |
| pm.done(); |
| } |
| } |
| |
| private RefactoringStatus checkExpressionBinding() throws JavaModelException { |
| return checkExpressionFragmentIsRValue(); |
| } |
| |
| private RefactoringStatus checkExpressionFragmentIsRValue() throws JavaModelException { |
| /* Moved this functionality to Checks, to allow sharing with |
| ExtractTempRefactoring, others */ |
| switch(Checks.checkExpressionIsRValue(getSelectedExpression().getAssociatedExpression())) { |
| case Checks.NOT_RVALUE_MISC: |
| return RefactoringStatus.createStatus(RefactoringStatus.FATAL, RefactoringCoreMessages.ExtractConstantRefactoring_select_expression, null, Corext.getPluginId(), RefactoringStatusCodes.EXPRESSION_NOT_RVALUE, null); |
| case Checks.NOT_RVALUE_VOID: |
| return RefactoringStatus.createStatus(RefactoringStatus.FATAL, RefactoringCoreMessages.ExtractConstantRefactoring_no_void, null, Corext.getPluginId(), RefactoringStatusCodes.EXPRESSION_NOT_RVALUE_VOID, null); |
| case Checks.IS_RVALUE_GUESSED: |
| case Checks.IS_RVALUE: |
| return new RefactoringStatus(); |
| default: |
| Assert.isTrue(false); return null; |
| } |
| } |
| |
| private ITypeBinding guessBindingForReference(Expression expression) { |
| ITypeBinding binding= expression.resolveTypeBinding(); |
| if (binding == null) { |
| binding= ASTResolving.guessBindingForReference(expression); |
| } |
| return binding; |
| } |
| |
| // !!! -- same as in ExtractTempRefactoring |
| private boolean isLiteralNodeSelected() throws JavaModelException { |
| IExpressionFragment fragment= getSelectedExpression(); |
| if (fragment == null) |
| return false; |
| Expression expression= fragment.getAssociatedExpression(); |
| if (expression == null) |
| return false; |
| switch (expression.getNodeType()) { |
| case ASTNode.BOOLEAN_LITERAL : |
| case ASTNode.CHARACTER_LITERAL : |
| case ASTNode.NULL_LITERAL : |
| case ASTNode.NUMBER_LITERAL : |
| return true; |
| |
| default : |
| return false; |
| } |
| } |
| |
| private RefactoringStatus checkExpression() throws JavaModelException { |
| RefactoringStatus result= new RefactoringStatus(); |
| result.merge(checkExpressionBinding()); |
| if(result.hasFatalError()) |
| return result; |
| checkAllStaticFinal(); |
| |
| IExpressionFragment selectedExpression= getSelectedExpression(); |
| Expression associatedExpression= selectedExpression.getAssociatedExpression(); |
| if (associatedExpression instanceof NullLiteral) |
| result.merge(RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractConstantRefactoring_null_literals)); |
| else if (!ConstantChecks.isLoadTimeConstant(selectedExpression)) |
| result.merge(RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractConstantRefactoring_not_load_time_constant)); |
| else if (associatedExpression instanceof SimpleName) { |
| if (associatedExpression.getParent() instanceof QualifiedName && associatedExpression.getLocationInParent() == QualifiedName.NAME_PROPERTY |
| || associatedExpression.getParent() instanceof FieldAccess && associatedExpression.getLocationInParent() == FieldAccess.NAME_PROPERTY) |
| return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractConstantRefactoring_select_expression); |
| } |
| |
| return result; |
| } |
| |
| public void setConstantName(String newName) { |
| Assert.isNotNull(newName); |
| fConstantName= newName; |
| } |
| |
| public String getConstantName() { |
| return fConstantName; |
| } |
| |
| /** |
| * This method performs checks on the constant name which are |
| * quick enough to be performed every time the ui input component |
| * contents are changed. |
| * |
| * @return return the resulting status |
| * @throws JavaModelException thrown when the operation could not be executed |
| */ |
| public RefactoringStatus checkConstantNameOnChange() throws JavaModelException { |
| if (Arrays.asList(getExcludedVariableNames()).contains(fConstantName)) |
| return RefactoringStatus.createErrorStatus(Messages.format(RefactoringCoreMessages.ExtractConstantRefactoring_another_variable, BasicElementLabels.getJavaElementName(getConstantName()))); |
| return Checks.checkConstantName(fConstantName, fCu); |
| } |
| |
| // !! similar to ExtractTempRefactoring equivalent |
| public String getConstantSignaturePreview() throws JavaModelException { |
| String space= " "; //$NON-NLS-1$ |
| return getVisibility() + space + MODIFIER + space + getConstantTypeName() + space + fConstantName; |
| } |
| |
| public CompilationUnitChange createTextChange(IProgressMonitor pm) throws CoreException { |
| createConstantDeclaration(); |
| replaceExpressionsWithConstant(); |
| return fCuRewrite.createChange(RefactoringCoreMessages.ExtractConstantRefactoring_change_name, true, pm); |
| } |
| |
| |
| @Override |
| public RefactoringStatus checkFinalConditions(IProgressMonitor pm) throws CoreException { |
| pm.beginTask(RefactoringCoreMessages.ExtractConstantRefactoring_checking_preconditions, 2); |
| |
| /* Note: some checks are performed on change of input widget |
| * values. (e.g. see ExtractConstantRefactoring.checkConstantNameOnChange()) |
| */ |
| |
| //TODO: possibly add more checking for name conflicts that might |
| // lead to a change in behavior |
| |
| try { |
| createConstantDeclaration(); |
| replaceExpressionsWithConstant(); |
| fChange= fCuRewrite.createChange(RefactoringCoreMessages.ExtractConstantRefactoring_change_name, true, new SubProgressMonitor(pm, 1)); |
| |
| return fCheckResultForCompileProblems ? RefactoringAnalyzeUtil.checkNewSource(fChange, fCu, fCuRewrite.getRoot(), pm) : new RefactoringStatus(); |
| } finally { |
| fConstantTypeCache= null; |
| fCuRewrite.clearASTAndImportRewrites(); |
| pm.done(); |
| } |
| } |
| |
| private void createConstantDeclaration() throws CoreException { |
| Type type= getConstantType(); |
| |
| IExpressionFragment fragment= getSelectedExpression(); |
| Expression initializer= getSelectedExpression().createCopyTarget(fCuRewrite.getASTRewrite(), true); |
| |
| AST ast= fCuRewrite.getAST(); |
| VariableDeclarationFragment variableDeclarationFragment= ast.newVariableDeclarationFragment(); |
| variableDeclarationFragment.setName(ast.newSimpleName(fConstantName)); |
| variableDeclarationFragment.setInitializer(initializer); |
| |
| FieldDeclaration fieldDeclaration= ast.newFieldDeclaration(variableDeclarationFragment); |
| fieldDeclaration.setType(type); |
| Modifier.ModifierKeyword accessModifier= Modifier.ModifierKeyword.toKeyword(fVisibility); |
| if (accessModifier != null) |
| fieldDeclaration.modifiers().add(ast.newModifier(accessModifier)); |
| fieldDeclaration.modifiers().add(ast.newModifier(Modifier.ModifierKeyword.STATIC_KEYWORD)); |
| fieldDeclaration.modifiers().add(ast.newModifier(Modifier.ModifierKeyword.FINAL_KEYWORD)); |
| |
| boolean createComments= JavaPreferencesSettings.getCodeGenerationSettings(fCu.getJavaProject()).createComments; |
| if (createComments) { |
| String comment= CodeGeneration.getFieldComment(fCu, getConstantTypeName(), fConstantName, StubUtility.getLineDelimiterUsed(fCu)); |
| if (comment != null && comment.length() > 0) { |
| Javadoc doc= (Javadoc) fCuRewrite.getASTRewrite().createStringPlaceholder(comment, ASTNode.JAVADOC); |
| fieldDeclaration.setJavadoc(doc); |
| } |
| } |
| |
| AbstractTypeDeclaration parent= getContainingTypeDeclarationNode(); |
| ListRewrite listRewrite= fCuRewrite.getASTRewrite().getListRewrite(parent, parent.getBodyDeclarationsProperty()); |
| TextEditGroup msg= fCuRewrite.createGroupDescription(RefactoringCoreMessages.ExtractConstantRefactoring_declare_constant); |
| if (insertFirst()) { |
| listRewrite.insertFirst(fieldDeclaration, msg); |
| } else { |
| listRewrite.insertAfter(fieldDeclaration, getNodeToInsertConstantDeclarationAfter(), msg); |
| } |
| |
| if (fLinkedProposalModel != null) { |
| ASTRewrite rewrite= fCuRewrite.getASTRewrite(); |
| LinkedProposalPositionGroup nameGroup= fLinkedProposalModel.getPositionGroup(KEY_NAME, true); |
| nameGroup.addPosition(rewrite.track(variableDeclarationFragment.getName()), true); |
| |
| String[] nameSuggestions= guessConstantNames(); |
| if (nameSuggestions.length > 0 && !nameSuggestions[0].equals(fConstantName)) { |
| nameGroup.addProposal(fConstantName, null, nameSuggestions.length + 1); |
| } |
| for (int i= 0; i < nameSuggestions.length; i++) { |
| nameGroup.addProposal(nameSuggestions[i], null, nameSuggestions.length - i); |
| } |
| |
| LinkedProposalPositionGroup typeGroup= fLinkedProposalModel.getPositionGroup(KEY_TYPE, true); |
| typeGroup.addPosition(rewrite.track(type), true); |
| |
| ITypeBinding typeBinding= guessBindingForReference(fragment.getAssociatedExpression()); |
| if (typeBinding != null) { |
| ITypeBinding[] relaxingTypes= ASTResolving.getNarrowingTypes(ast, typeBinding); |
| for (int i= 0; i < relaxingTypes.length; i++) { |
| typeGroup.addProposal(relaxingTypes[i], fCuRewrite.getCu(), relaxingTypes.length - i); |
| } |
| } |
| boolean isInterface= parent.resolveBinding() != null && parent.resolveBinding().isInterface(); |
| ModifierCorrectionSubProcessorCore.installLinkedVisibilityProposals(fLinkedProposalModel, rewrite, fieldDeclaration.modifiers(), isInterface); |
| } |
| } |
| |
| private Type getConstantType() throws JavaModelException { |
| if (fConstantTypeCache == null) { |
| IExpressionFragment fragment= getSelectedExpression(); |
| ITypeBinding typeBinding= guessBindingForReference(fragment.getAssociatedExpression()); |
| AST ast= fCuRewrite.getAST(); |
| typeBinding= Bindings.normalizeForDeclarationUse(typeBinding, ast); |
| ImportRewrite importRewrite= fCuRewrite.getImportRewrite(); |
| ImportRewriteContext context= new ContextSensitiveImportRewriteContext(fCuRewrite.getRoot(), fSelectionStart, importRewrite); |
| fConstantTypeCache= importRewrite.addImport(typeBinding, ast, context, TypeLocation.FIELD); |
| } |
| return fConstantTypeCache; |
| } |
| |
| @Override |
| public Change createChange(IProgressMonitor monitor) throws CoreException { |
| ExtractConstantDescriptor descriptor= createRefactoringDescriptor(); |
| fChange.setDescriptor(new RefactoringChangeDescriptor(descriptor)); |
| return fChange; |
| } |
| |
| private ExtractConstantDescriptor createRefactoringDescriptor() { |
| final Map<String, String> arguments= new HashMap<>(); |
| String project= null; |
| IJavaProject javaProject= fCu.getJavaProject(); |
| if (javaProject != null) |
| project= javaProject.getElementName(); |
| int flags= JavaRefactoringDescriptor.JAR_REFACTORING | JavaRefactoringDescriptor.JAR_SOURCE_ATTACHMENT; |
| if (JdtFlags.getVisibilityCode(fVisibility) != Modifier.PRIVATE) |
| flags|= RefactoringDescriptor.STRUCTURAL_CHANGE; |
| |
| final String expression= ASTNodes.asString(fSelectedExpression.getAssociatedExpression()); |
| final String description= Messages.format(RefactoringCoreMessages.ExtractConstantRefactoring_descriptor_description_short, BasicElementLabels.getJavaElementName(fConstantName)); |
| final String header= Messages.format(RefactoringCoreMessages.ExtractConstantRefactoring_descriptor_description, new String[] { BasicElementLabels.getJavaElementName(fConstantName), BasicElementLabels.getJavaCodeString(expression)}); |
| final JDTRefactoringDescriptorComment comment= new JDTRefactoringDescriptorComment(project, this, header); |
| comment.addSetting(Messages.format(RefactoringCoreMessages.ExtractConstantRefactoring_constant_name_pattern, BasicElementLabels.getJavaElementName(fConstantName))); |
| comment.addSetting(Messages.format(RefactoringCoreMessages.ExtractConstantRefactoring_constant_expression_pattern, BasicElementLabels.getJavaCodeString(expression))); |
| String visibility= fVisibility; |
| if ("".equals(visibility)) //$NON-NLS-1$ |
| visibility= RefactoringCoreMessages.ExtractConstantRefactoring_default_visibility; |
| comment.addSetting(Messages.format(RefactoringCoreMessages.ExtractConstantRefactoring_visibility_pattern, visibility)); |
| if (fReplaceAllOccurrences) |
| comment.addSetting(RefactoringCoreMessages.ExtractConstantRefactoring_replace_occurrences); |
| if (fQualifyReferencesWithDeclaringClassName) |
| comment.addSetting(RefactoringCoreMessages.ExtractConstantRefactoring_qualify_references); |
| arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT, JavaRefactoringDescriptorUtil.elementToHandle(project, fCu)); |
| arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME, fConstantName); |
| arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION, Integer.valueOf(fSelectionStart).toString() + " " + Integer.valueOf(fSelectionLength).toString()); //$NON-NLS-1$ |
| arguments.put(ATTRIBUTE_REPLACE, Boolean.valueOf(fReplaceAllOccurrences).toString()); |
| arguments.put(ATTRIBUTE_QUALIFY, Boolean.valueOf(fQualifyReferencesWithDeclaringClassName).toString()); |
| arguments.put(ATTRIBUTE_VISIBILITY, Integer.valueOf(JdtFlags.getVisibilityCode(fVisibility)).toString()); |
| |
| ExtractConstantDescriptor descriptor= RefactoringSignatureDescriptorFactory.createExtractConstantDescriptor(project, description, comment.asString(), arguments, flags); |
| return descriptor; |
| } |
| |
| private void replaceExpressionsWithConstant() throws JavaModelException { |
| ASTRewrite astRewrite= fCuRewrite.getASTRewrite(); |
| AST ast= astRewrite.getAST(); |
| |
| for (IASTFragment fragment : getFragmentsToReplace()) { |
| ASTNode node= fragment.getAssociatedNode(); |
| boolean inTypeDeclarationAnnotation= isInTypeDeclarationAnnotation(node); |
| if (inTypeDeclarationAnnotation && JdtFlags.VISIBILITY_STRING_PRIVATE == getVisibility()) |
| continue; |
| |
| SimpleName ref= ast.newSimpleName(fConstantName); |
| Name replacement= ref; |
| boolean qualifyReference= qualifyReferencesWithDeclaringClassName(); |
| if (!qualifyReference) { |
| qualifyReference= inTypeDeclarationAnnotation; |
| } |
| if (qualifyReference) { |
| replacement= ast.newQualifiedName(ast.newSimpleName(getContainingTypeBinding().getName()), ref); |
| } |
| TextEditGroup description= fCuRewrite.createGroupDescription(RefactoringCoreMessages.ExtractConstantRefactoring_replace); |
| |
| fragment.replace(astRewrite, replacement, description); |
| if (fLinkedProposalModel != null) |
| fLinkedProposalModel.getPositionGroup(KEY_NAME, true).addPosition(astRewrite.track(ref), false); |
| } |
| } |
| |
| private boolean isInTypeDeclarationAnnotation(ASTNode node) throws JavaModelException { |
| Annotation enclosingAnnotation= ASTNodes.getParent(node, Annotation.class); |
| return enclosingAnnotation != null && enclosingAnnotation.getParent() == getContainingTypeDeclarationNode(); |
| } |
| |
| private void computeConstantDeclarationLocation() throws JavaModelException { |
| if (isDeclarationLocationComputed()) |
| return; |
| |
| BodyDeclaration lastStaticDependency= null; |
| Iterator<BodyDeclaration> decls= getContainingTypeDeclarationNode().bodyDeclarations().iterator(); |
| |
| while (decls.hasNext()) { |
| BodyDeclaration decl= decls.next(); |
| |
| int modifiers; |
| if (decl instanceof FieldDeclaration) |
| modifiers= ((FieldDeclaration) decl).getModifiers(); |
| else if (decl instanceof Initializer) |
| modifiers= ((Initializer) decl).getModifiers(); |
| else { |
| continue; /* this declaration is not a field declaration |
| or initializer, so the placement of the constant |
| declaration relative to it does not matter */ |
| } |
| |
| if (Modifier.isStatic(modifiers) && depends(getSelectedExpression(), decl)) |
| lastStaticDependency= decl; |
| } |
| |
| if(lastStaticDependency == null) |
| fInsertFirst= true; |
| else |
| fToInsertAfter= lastStaticDependency; |
| } |
| |
| /* bd is a static field declaration or static initializer */ |
| private static boolean depends(IExpressionFragment selected, BodyDeclaration bd) { |
| /* We currently consider selected to depend on bd only if db includes a declaration |
| * of a static field on which selected depends. |
| * |
| * A more accurate strategy might be to also check if bd contains (or is) a |
| * static initializer containing code which changes the value of a static field on |
| * which selected depends. However, if a static is written to multiple times within |
| * during class initialization, it is difficult to predict which value should be used. |
| * This would depend on which value is used by expressions instances for which the new |
| * constant will be substituted, and there may be many of these; in each, the |
| * static field in question may have taken on a different value (if some of these uses |
| * occur within static initializers). |
| */ |
| |
| if(bd instanceof FieldDeclaration) { |
| FieldDeclaration fieldDecl = (FieldDeclaration) bd; |
| for(Iterator<VariableDeclarationFragment> fragments = fieldDecl.fragments().iterator(); fragments.hasNext();) { |
| VariableDeclarationFragment fragment = fragments.next(); |
| SimpleName staticFieldName = fragment.getName(); |
| if(selected.getSubFragmentsMatching(ASTFragmentFactory.createFragmentForFullSubtree(staticFieldName)).length != 0) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private boolean isDeclarationLocationComputed() { |
| return fInsertFirst == true || fToInsertAfter != null; |
| } |
| |
| private boolean insertFirst() throws JavaModelException { |
| if(!isDeclarationLocationComputed()) |
| computeConstantDeclarationLocation(); |
| return fInsertFirst; |
| } |
| |
| private BodyDeclaration getNodeToInsertConstantDeclarationAfter() throws JavaModelException { |
| if(!isDeclarationLocationComputed()) |
| computeConstantDeclarationLocation(); |
| return fToInsertAfter; |
| } |
| |
| private String getConstantTypeName() throws JavaModelException { |
| return ASTNodes.asString(getConstantType()); |
| } |
| |
| private static boolean isStaticFieldOrStaticInitializer(BodyDeclaration node) { |
| if(node instanceof MethodDeclaration || node instanceof AbstractTypeDeclaration) |
| return false; |
| |
| int modifiers; |
| if(node instanceof FieldDeclaration) { |
| modifiers = ((FieldDeclaration) node).getModifiers(); |
| } else if(node instanceof Initializer) { |
| modifiers = ((Initializer) node).getModifiers(); |
| } else { |
| Assert.isTrue(false); |
| return false; |
| } |
| |
| if(!Modifier.isStatic(modifiers)) |
| return false; |
| |
| return true; |
| } |
| |
| /* |
| * Elements returned by next() are BodyDeclaration or Annotation instances. |
| */ |
| private Iterator<ASTNode> getReplacementScope() throws JavaModelException { |
| boolean declPredecessorReached= false; |
| |
| Collection<ASTNode> scope= new ArrayList<>(); |
| |
| AbstractTypeDeclaration containingType= getContainingTypeDeclarationNode(); |
| if (containingType instanceof EnumDeclaration) { |
| // replace in all enum constants bodies |
| EnumDeclaration enumDeclaration= (EnumDeclaration) containingType; |
| scope.addAll(enumDeclaration.enumConstants()); |
| } |
| |
| for (Iterator<IExtendedModifier> iter= containingType.modifiers().iterator(); iter.hasNext();) { |
| IExtendedModifier modifier= iter.next(); |
| if (modifier instanceof Annotation) { |
| scope.add((ASTNode) modifier); |
| } |
| } |
| |
| for (Iterator<BodyDeclaration> bodyDeclarations = containingType.bodyDeclarations().iterator(); bodyDeclarations.hasNext();) { |
| BodyDeclaration bodyDeclaration= bodyDeclarations.next(); |
| |
| if(bodyDeclaration == getNodeToInsertConstantDeclarationAfter()) |
| declPredecessorReached= true; |
| |
| if(insertFirst() || declPredecessorReached || !isStaticFieldOrStaticInitializer(bodyDeclaration)) |
| scope.add(bodyDeclaration); |
| } |
| return scope.iterator(); |
| } |
| |
| private IASTFragment[] getFragmentsToReplace() throws JavaModelException { |
| List<IASTFragment> toReplace = new ArrayList<>(); |
| if (fReplaceAllOccurrences) { |
| Iterator<ASTNode> replacementScope = getReplacementScope(); |
| while(replacementScope.hasNext()) { |
| ASTNode scope= replacementScope.next(); |
| IASTFragment[] allMatches= ASTFragmentFactory.createFragmentForFullSubtree(scope).getSubFragmentsMatching(getSelectedExpression()); |
| IASTFragment[] replaceableMatches = retainOnlyReplacableMatches(allMatches); |
| Collections.addAll(toReplace, replaceableMatches); |
| } |
| } else if (canReplace(getSelectedExpression())) |
| toReplace.add(getSelectedExpression()); |
| return toReplace.toArray(new IASTFragment[toReplace.size()]); |
| } |
| |
| // !! - like one in ExtractTempRefactoring |
| private static IASTFragment[] retainOnlyReplacableMatches(IASTFragment[] allMatches) { |
| List<IASTFragment> result= new ArrayList<>(allMatches.length); |
| for (IASTFragment match : allMatches) { |
| if (canReplace(match)) { |
| result.add(match); |
| } |
| } |
| return result.toArray(new IASTFragment[result.size()]); |
| } |
| |
| // !! - like one in ExtractTempRefactoring |
| private static boolean canReplace(IASTFragment fragment) { |
| ASTNode node= fragment.getAssociatedNode(); |
| ASTNode parent= node.getParent(); |
| if (parent instanceof VariableDeclarationFragment) { |
| VariableDeclarationFragment vdf= (VariableDeclarationFragment) parent; |
| if (node.equals(vdf.getName())) |
| return false; |
| } |
| if (parent instanceof ExpressionStatement) |
| return false; |
| if (parent instanceof SwitchCase) { |
| if (node instanceof Name) { |
| Name name= (Name) node; |
| ITypeBinding typeBinding= name.resolveTypeBinding(); |
| if (typeBinding != null) { |
| return !typeBinding.isEnum(); |
| } |
| } |
| } |
| return true; |
| } |
| |
| private IExpressionFragment getSelectedExpression() throws JavaModelException { |
| if(fSelectedExpression != null) |
| return fSelectedExpression; |
| |
| IASTFragment selectedFragment= ASTFragmentFactory.createFragmentForSourceRange(new SourceRange(fSelectionStart, fSelectionLength), fCuRewrite.getRoot(), fCu); |
| |
| if (selectedFragment instanceof IExpressionFragment |
| && ! Checks.isInsideJavadoc(selectedFragment.getAssociatedNode())) { |
| fSelectedExpression= (IExpressionFragment) selectedFragment; |
| } |
| |
| if (fSelectedExpression != null && Checks.isEnumCase(fSelectedExpression.getAssociatedExpression().getParent())) { |
| fSelectedExpression= null; |
| } |
| |
| return fSelectedExpression; |
| } |
| |
| /** |
| * Returns the type to which the new constant will be added to. It is the first non-anonymous parent. |
| * @return the type to add the new constant to |
| * |
| * @throws JavaModelException shouldn't happen |
| */ |
| private AbstractTypeDeclaration getContainingTypeDeclarationNode() throws JavaModelException { |
| AbstractTypeDeclaration result= ASTNodes.getParent(getSelectedExpression().getAssociatedNode(), AbstractTypeDeclaration.class); |
| Assert.isNotNull(result); |
| return result; |
| } |
| |
| private ITypeBinding getContainingTypeBinding() throws JavaModelException { |
| ITypeBinding result= getContainingTypeDeclarationNode().resolveBinding(); |
| Assert.isNotNull(result); |
| return result; |
| } |
| |
| private RefactoringStatus initialize(JavaRefactoringArguments arguments) { |
| final String selection= arguments.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION); |
| if (selection != null) { |
| int offset= -1; |
| int length= -1; |
| final StringTokenizer tokenizer= new StringTokenizer(selection); |
| if (tokenizer.hasMoreTokens()) |
| offset= Integer.valueOf(tokenizer.nextToken()).intValue(); |
| if (tokenizer.hasMoreTokens()) |
| length= Integer.valueOf(tokenizer.nextToken()).intValue(); |
| if (offset >= 0 && length >= 0) { |
| fSelectionStart= offset; |
| fSelectionLength= length; |
| } else |
| return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_illegal_argument, new Object[] { selection, JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION})); |
| } else |
| return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION)); |
| final String handle= arguments.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT); |
| if (handle != null) { |
| final IJavaElement element= JavaRefactoringDescriptorUtil.handleToElement(arguments.getProject(), handle, false); |
| if (element == null || !element.exists() || element.getElementType() != IJavaElement.COMPILATION_UNIT) |
| return JavaRefactoringDescriptorUtil.createInputFatalStatus(element, getName(), IJavaRefactorings.EXTRACT_CONSTANT); |
| else |
| fCu= (ICompilationUnit) element; |
| } else |
| return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT)); |
| final String visibility= arguments.getAttribute(ATTRIBUTE_VISIBILITY); |
| if (visibility != null && !"".equals(visibility)) {//$NON-NLS-1$ |
| int flag= 0; |
| try { |
| flag= Integer.parseInt(visibility); |
| } catch (NumberFormatException exception) { |
| return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_VISIBILITY)); |
| } |
| fVisibility= JdtFlags.getVisibilityString(flag); |
| } |
| final String name= arguments.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME); |
| if (name != null && !"".equals(name)) //$NON-NLS-1$ |
| fConstantName= name; |
| else |
| return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME)); |
| final String replace= arguments.getAttribute(ATTRIBUTE_REPLACE); |
| if (replace != null) { |
| fReplaceAllOccurrences= Boolean.valueOf(replace).booleanValue(); |
| } else |
| return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_REPLACE)); |
| final String declareFinal= arguments.getAttribute(ATTRIBUTE_QUALIFY); |
| if (declareFinal != null) { |
| fQualifyReferencesWithDeclaringClassName= Boolean.valueOf(declareFinal).booleanValue(); |
| } else |
| return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_QUALIFY)); |
| return new RefactoringStatus(); |
| } |
| } |