blob: 9c1a5a7de672e855c849d9eef83f7ddd390fb191 [file] [log] [blame]
/*******************************************************************************
* 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();
}
}