blob: 981d6e77682b0b413bf2c074753d5ea2f3a3cbcd [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
* Microsoft Corporation - copied to jdt.core.manipulation
*******************************************************************************/
package org.eclipse.jdt.internal.corext.refactoring.code;
import java.util.ArrayList;
import java.util.Arrays;
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.text.edits.TextEdit;
import org.eclipse.jface.text.Document;
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.ltk.core.refactoring.RefactoringStatusContext;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
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.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.ArrayCreation;
import org.eclipse.jdt.core.dom.ArrayInitializer;
import org.eclipse.jdt.core.dom.ArrayType;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.CatchClause;
import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ConstructorInvocation;
import org.eclipse.jdt.core.dom.Dimension;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IExtendedModifier;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
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.SimpleName;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.SwitchStatement;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
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.ConvertLocalVariableDescriptor;
import org.eclipse.jdt.internal.core.manipulation.BindingLabelProviderCore;
import org.eclipse.jdt.internal.core.manipulation.JavaElementLabelsCore;
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.codemanipulation.ContextSensitiveImportRewriteContext;
import org.eclipse.jdt.internal.corext.dom.ASTNodeFactory;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.dom.DimensionRewrite;
import org.eclipse.jdt.internal.corext.dom.HierarchicalASTVisitor;
import org.eclipse.jdt.internal.corext.dom.ModifierRewrite;
import org.eclipse.jdt.internal.corext.fix.LinkedProposalModelCore;
import org.eclipse.jdt.internal.corext.fix.LinkedProposalPositionGroupCore;
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.changes.TextChangeCompatibility;
import org.eclipse.jdt.internal.corext.refactoring.rename.TempDeclarationFinder;
import org.eclipse.jdt.internal.corext.refactoring.rename.TempOccurrenceAnalyzer;
import org.eclipse.jdt.internal.corext.refactoring.util.JavaStatusContext;
import org.eclipse.jdt.internal.corext.refactoring.util.RefactoringASTParser;
import org.eclipse.jdt.internal.corext.refactoring.util.ResourceUtil;
import org.eclipse.jdt.internal.corext.util.JdtFlags;
import org.eclipse.jdt.internal.corext.util.Messages;
public class PromoteTempToFieldRefactoring extends Refactoring {
private static final String ATTRIBUTE_STATIC= "static"; //$NON-NLS-1$
private static final String ATTRIBUTE_FINAL= "final"; //$NON-NLS-1$
private static final String ATTRIBUTE_VISIBILITY= "visibility"; //$NON-NLS-1$
private static final String ATTRIBUTE_INITIALIZE= "initialize"; //$NON-NLS-1$
private int fSelectionStart;
private int fSelectionLength;
private ICompilationUnit fCu;
public static final int INITIALIZE_IN_FIELD= 0;
public static final int INITIALIZE_IN_METHOD= 1;
public static final int INITIALIZE_IN_CONSTRUCTOR= 2;
private static final String LINKED_NAME= "name"; //$NON-NLS-1$
//------ settings ---------//
private String fFieldName;
private int fVisibility; /*see Modifier*/
private boolean fDeclareStatic;
private boolean fDeclareFinal;
private int fInitializeIn; /*see INITIALIZE_IN_* constraints */
//------ fields used for computations ---------//
private CompilationUnit fCompilationUnitNode;
private VariableDeclaration fTempDeclarationNode;
//------ analysis ---------//
private boolean fInitializerUsesLocalTypes;
private boolean fTempTypeUsesClassTypeVariables;
//------ scripting --------//
private boolean fSelfInitializing= false;
private LinkedProposalModelCore fLinkedProposalModel;
private Map<String, String> fFormatterOptions;
/**
* Creates a new promote temp to field refactoring.
* @param unit the compilation unit, or <code>null</code> if invoked by scripting
* @param selectionStart start
* @param selectionLength length
*/
public PromoteTempToFieldRefactoring(ICompilationUnit unit, int selectionStart, int selectionLength){
Assert.isTrue(selectionStart >= 0);
Assert.isTrue(selectionLength >= 0);
fSelectionStart= selectionStart;
fSelectionLength= selectionLength;
fCu= unit;
fFieldName= ""; //$NON-NLS-1$
fVisibility= Modifier.PRIVATE;
fDeclareStatic= false;
fDeclareFinal= false;
fInitializeIn= INITIALIZE_IN_METHOD;
fLinkedProposalModel= null;
}
/**
* Creates a new promote temp to field refactoring.
* @param declaration the variable declaration node to convert to a field
*/
public PromoteTempToFieldRefactoring(VariableDeclaration declaration) {
Assert.isTrue(declaration != null);
fTempDeclarationNode= declaration;
IVariableBinding resolveBinding= declaration.resolveBinding();
Assert.isTrue(resolveBinding != null && !resolveBinding.isParameter() && !resolveBinding.isField());
ASTNode root= declaration.getRoot();
Assert.isTrue(root instanceof CompilationUnit);
fCompilationUnitNode= (CompilationUnit) root;
IJavaElement input= fCompilationUnitNode.getJavaElement();
Assert.isTrue(input instanceof ICompilationUnit);
fCu= (ICompilationUnit) input;
fSelectionStart= declaration.getStartPosition();
fSelectionLength= declaration.getLength();
fFieldName= ""; //$NON-NLS-1$
fVisibility= Modifier.PRIVATE;
fDeclareStatic= false;
fDeclareFinal= false;
fInitializeIn= INITIALIZE_IN_METHOD;
fLinkedProposalModel= null;
}
public PromoteTempToFieldRefactoring(JavaRefactoringArguments arguments, RefactoringStatus status) {
this(null);
RefactoringStatus initializeStatus= initialize(arguments);
status.merge(initializeStatus);
}
@Override
public String getName() {
return RefactoringCoreMessages.PromoteTempToFieldRefactoring_name;
}
public int[] getAvailableVisibilities(){
return new int[]{Modifier.PUBLIC, Modifier.PROTECTED, Modifier.NONE, Modifier.PRIVATE};
}
public int getVisibility() {
return fVisibility;
}
public boolean getDeclareFinal() {
return fDeclareFinal;
}
public boolean getDeclareStatic() {
return fDeclareStatic;
}
public int getInitializeIn() {
return fInitializeIn;
}
public void setVisibility(int accessModifier) {
Assert.isTrue(accessModifier == Modifier.PRIVATE ||
accessModifier == Modifier.NONE ||
accessModifier == Modifier.PROTECTED ||
accessModifier == Modifier.PUBLIC);
fVisibility= accessModifier;
}
public void setDeclareFinal(boolean declareFinal) {
fDeclareFinal= declareFinal;
}
public void setDeclareStatic(boolean declareStatic) {
fDeclareStatic= declareStatic;
}
public void setFieldName(String fieldName) {
Assert.isNotNull(fieldName);
fFieldName= fieldName;
}
public void setInitializeIn(int initializeIn) {
Assert.isTrue( initializeIn == INITIALIZE_IN_CONSTRUCTOR ||
initializeIn == INITIALIZE_IN_FIELD ||
initializeIn == INITIALIZE_IN_METHOD);
fInitializeIn= initializeIn;
}
public boolean canEnableSettingStatic(){
return fInitializeIn != INITIALIZE_IN_CONSTRUCTOR &&
! isTempDeclaredInStaticMethod() &&
! fTempTypeUsesClassTypeVariables;
}
public boolean canEnableSettingFinal(){
if (fInitializeIn == INITIALIZE_IN_CONSTRUCTOR)
return canEnableSettingDeclareInConstructors() && ! tempHasAssignmentsOtherThanInitialization();
else if (fInitializeIn == INITIALIZE_IN_FIELD)
return canEnableSettingDeclareInFieldDeclaration() && ! tempHasAssignmentsOtherThanInitialization();
else if (getMethodDeclaration().isConstructor())
return !tempHasAssignmentsOtherThanInitialization();
else
return false;
}
private boolean tempHasAssignmentsOtherThanInitialization() {
TempAssignmentFinder assignmentFinder= new TempAssignmentFinder(fTempDeclarationNode);
fCompilationUnitNode.accept(assignmentFinder);
return assignmentFinder.hasAssignments();
}
public boolean canEnableSettingDeclareInConstructors(){
return ! fDeclareStatic &&
! fInitializerUsesLocalTypes &&
! getMethodDeclaration().isConstructor() &&
! isDeclaredInAnonymousClass() &&
! isTempDeclaredInStaticMethod() &&
tempHasInitializer();
}
public boolean canEnableSettingDeclareInMethod(){
return ! fDeclareFinal &&
tempHasInitializer();
}
private boolean tempHasInitializer() {
return getTempInitializer() != null;
}
public boolean canEnableSettingDeclareInFieldDeclaration(){
return ! fInitializerUsesLocalTypes && tempHasInitializer();
}
private Expression getTempInitializer() {
return fTempDeclarationNode.getInitializer();
}
private boolean isTempDeclaredInStaticMethod() {
return Modifier.isStatic(getMethodDeclaration().getModifiers());
}
private MethodDeclaration getMethodDeclaration(){
return ASTNodes.getParent(fTempDeclarationNode, MethodDeclaration.class);
}
private boolean isDeclaredInAnonymousClass() {
return null != ASTNodes.getParent(fTempDeclarationNode, AnonymousClassDeclaration.class);
}
/*
* @see org.eclipse.jdt.internal.corext.refactoring.base.Refactoring#checkActivation(org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException {
RefactoringStatus result= Checks.validateModifiesFiles(
ResourceUtil.getFiles(new ICompilationUnit[]{fCu}),
getValidationContext());
if (result.hasFatalError())
return result;
initAST(pm);
if (fTempDeclarationNode == null)
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.PromoteTempToFieldRefactoring_select_declaration);
if (! Checks.isDeclaredIn(fTempDeclarationNode, MethodDeclaration.class))
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.PromoteTempToFieldRefactoring_only_declared_in_methods);
if (isMethodParameter())
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.PromoteTempToFieldRefactoring_method_parameters);
if (isTempAnExceptionInCatchBlock())
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.PromoteTempToFieldRefactoring_exceptions);
ASTNode declaringType= ASTResolving.findParentType(fTempDeclarationNode);
if (declaringType instanceof TypeDeclaration && ((TypeDeclaration) declaringType).isInterface())
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.PromoteTempToFieldRefactoring_interface_methods);
result.merge(checkTempTypeForLocalTypeUsage());
if (result.hasFatalError())
return result;
checkTempInitializerForLocalTypeUsage();
if (!fSelfInitializing)
initializeDefaults();
return result;
}
private void initializeDefaults() {
fVisibility= Modifier.PRIVATE;
fDeclareStatic= Modifier.isStatic(getMethodDeclaration().getModifiers());
fDeclareFinal= false;
if (canEnableSettingDeclareInMethod())
fInitializeIn= INITIALIZE_IN_METHOD;
else if (canEnableSettingDeclareInFieldDeclaration())
fInitializeIn= INITIALIZE_IN_FIELD;
else if (canEnableSettingDeclareInConstructors())
fInitializeIn= INITIALIZE_IN_CONSTRUCTOR;
}
public String[] guessFieldNames() {
String rawTempName= StubUtility.getBaseName(fTempDeclarationNode.resolveBinding(), fCu.getJavaProject());
String[] excludedNames= getNamesOfFieldsInDeclaringType();
int dim= ASTNodes.getDimensions(fTempDeclarationNode);
return StubUtility.getFieldNameSuggestions(fCu.getJavaProject(), rawTempName, dim, getModifiers(), excludedNames);
}
private String getInitialFieldName() {
String[] suggestedNames= guessFieldNames();
if (suggestedNames.length > 0) {
if (fLinkedProposalModel != null) {
LinkedProposalPositionGroupCore nameGroup= fLinkedProposalModel.getPositionGroup(LINKED_NAME, true);
for (int i= 0; i < suggestedNames.length; i++) {
nameGroup.addProposal(suggestedNames[i], suggestedNames.length - i);
}
}
return suggestedNames[0];
} else {
return fTempDeclarationNode.getName().getIdentifier();
}
}
private String[] getNamesOfFieldsInDeclaringType() {
final AbstractTypeDeclaration type= getEnclosingType();
if (type instanceof TypeDeclaration) {
FieldDeclaration[] fields= ((TypeDeclaration) type).getFields();
List<String> result= new ArrayList<>(fields.length);
for (int i= 0; i < fields.length; i++) {
for (Iterator<VariableDeclarationFragment> iter= fields[i].fragments().iterator(); iter.hasNext();) {
VariableDeclarationFragment field= iter.next();
result.add(field.getName().getIdentifier());
}
}
return result.toArray(new String[result.size()]);
}
return new String[] {};
}
private void checkTempInitializerForLocalTypeUsage() {
Expression initializer= fTempDeclarationNode.getInitializer();
if (initializer == null)
return;
IMethodBinding declaringMethodBinding= getMethodDeclaration().resolveBinding();
ITypeBinding[] methodTypeParameters= declaringMethodBinding == null ? new ITypeBinding[0] : declaringMethodBinding.getTypeParameters();
LocalTypeAndVariableUsageAnalyzer localTypeAnalyer= new LocalTypeAndVariableUsageAnalyzer(methodTypeParameters);
initializer.accept(localTypeAnalyer);
fInitializerUsesLocalTypes= ! localTypeAnalyer.getUsageOfEnclosingNodes().isEmpty();
}
private RefactoringStatus checkTempTypeForLocalTypeUsage(){
VariableDeclarationStatement vds= getTempDeclarationStatement();
if (vds == null)
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.PromoteTempToFieldRefactoring_cannot_promote);
Type type= vds.getType();
ITypeBinding binding= type.resolveBinding();
if (binding == null)
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.PromoteTempToFieldRefactoring_cannot_promote);
IMethodBinding declaringMethodBinding= getMethodDeclaration().resolveBinding();
ITypeBinding[] methodTypeParameters= declaringMethodBinding == null ? new ITypeBinding[0] : declaringMethodBinding.getTypeParameters();
LocalTypeAndVariableUsageAnalyzer analyzer= new LocalTypeAndVariableUsageAnalyzer(methodTypeParameters);
type.accept(analyzer);
boolean usesLocalTypes= ! analyzer.getUsageOfEnclosingNodes().isEmpty();
fTempTypeUsesClassTypeVariables= analyzer.getClassTypeVariablesUsed();
if (usesLocalTypes)
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.PromoteTempToFieldRefactoring_uses_type_declared_locally);
return null;
}
private VariableDeclarationStatement getTempDeclarationStatement() {
return ASTNodes.getParent(fTempDeclarationNode, VariableDeclarationStatement.class);
}
private boolean isTempAnExceptionInCatchBlock() {
return (fTempDeclarationNode.getParent() instanceof CatchClause);
}
private boolean isMethodParameter() {
return (fTempDeclarationNode.getParent() instanceof MethodDeclaration);
}
private void initAST(IProgressMonitor pm){
if (fCompilationUnitNode == null) {
fCompilationUnitNode= RefactoringASTParser.parseWithASTProvider(fCu, true, pm);
fTempDeclarationNode= TempDeclarationFinder.findTempDeclaration(fCompilationUnitNode, fSelectionStart, fSelectionLength);
}
}
public RefactoringStatus validateInput(){
return Checks.checkFieldName(fFieldName, fCu);
}
/*
* @see org.eclipse.jdt.internal.corext.refactoring.base.Refactoring#checkInput(org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
public RefactoringStatus checkFinalConditions(IProgressMonitor pm) throws CoreException {
try{
RefactoringStatus result= new RefactoringStatus();
result.merge(checkClashesWithExistingFields());
if (fInitializeIn == INITIALIZE_IN_CONSTRUCTOR)
result.merge(checkClashesInConstructors());
return result;
} finally {
pm.done();
}
}
private RefactoringStatus checkClashesInConstructors() {
Assert.isTrue(fInitializeIn == INITIALIZE_IN_CONSTRUCTOR);
Assert.isTrue(!isDeclaredInAnonymousClass());
final AbstractTypeDeclaration declaration= (AbstractTypeDeclaration) getMethodDeclaration().getParent();
if (declaration instanceof TypeDeclaration) {
MethodDeclaration[] methods= ((TypeDeclaration) declaration).getMethods();
for (int i= 0; i < methods.length; i++) {
MethodDeclaration method= methods[i];
if (!method.isConstructor())
continue;
NameCollector nameCollector= new NameCollector(method) {
@Override
protected boolean visitNode(ASTNode node) {
return true;
}
};
method.accept(nameCollector);
List<String> names= nameCollector.getNames();
if (names.contains(fFieldName)) {
String[] keys= { BasicElementLabels.getJavaElementName(fFieldName), BindingLabelProviderCore.getBindingLabel(method.resolveBinding(), JavaElementLabelsCore.ALL_FULLY_QUALIFIED)};
String msg= Messages.format(RefactoringCoreMessages.PromoteTempToFieldRefactoring_Name_conflict, keys);
return RefactoringStatus.createFatalErrorStatus(msg);
}
}
}
return null;
}
private RefactoringStatus checkClashesWithExistingFields(){
FieldDeclaration[] existingFields= getFieldDeclarations();
for (int i= 0; i < existingFields.length; i++) {
FieldDeclaration declaration= existingFields[i];
VariableDeclarationFragment[] fragments= (VariableDeclarationFragment[]) declaration.fragments().toArray(new VariableDeclarationFragment[declaration.fragments().size()]);
for (int j= 0; j < fragments.length; j++) {
VariableDeclarationFragment fragment= fragments[j];
if (fFieldName.equals(fragment.getName().getIdentifier())){
//cannot conflict with more than 1 name
RefactoringStatusContext context= JavaStatusContext.create(fCu, fragment);
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.PromoteTempToFieldRefactoring_Name_conflict_with_field, context);
}
}
}
return null;
}
private FieldDeclaration[] getFieldDeclarations() {
List<BodyDeclaration> bodyDeclarations= ASTNodes.getBodyDeclarations(getMethodDeclaration().getParent());
List<FieldDeclaration> fields= new ArrayList<>(1);
for (Iterator<BodyDeclaration> iter= bodyDeclarations.iterator(); iter.hasNext();) {
Object each= iter.next();
if (each instanceof FieldDeclaration)
fields.add((FieldDeclaration) each);
}
return fields.toArray(new FieldDeclaration[fields.size()]);
}
/*
* @see org.eclipse.jdt.internal.corext.refactoring.base.IRefactoring#createChange(org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
public Change createChange(IProgressMonitor pm) throws CoreException {
pm.beginTask("", 1); //$NON-NLS-1$
try {
if (fFieldName.length() == 0) {
fFieldName= getInitialFieldName();
}
ASTRewrite rewrite= ASTRewrite.create(fCompilationUnitNode.getAST());
if (fInitializeIn == INITIALIZE_IN_METHOD && tempHasInitializer())
addLocalDeclarationSplit(rewrite);
else
addLocalDeclarationRemoval(rewrite);
if (fInitializeIn == INITIALIZE_IN_CONSTRUCTOR)
addInitializersToConstructors(rewrite);
addTempRenames(rewrite);
addFieldDeclaration(rewrite);
CompilationUnitChange result= new CompilationUnitChange(RefactoringCoreMessages.PromoteTempToFieldRefactoring_name, fCu);
result.setDescriptor(new RefactoringChangeDescriptor(getRefactoringDescriptor()));
TextEdit resultingEdits;
if (fFormatterOptions == null) {
resultingEdits= rewrite.rewriteAST();
} else {
resultingEdits= rewrite.rewriteAST(new Document(fCu.getSource()), fFormatterOptions);
}
TextChangeCompatibility.addTextEdit(result, RefactoringCoreMessages.PromoteTempToFieldRefactoring_editName, resultingEdits);
return result;
} finally {
pm.done();
}
}
private void addTempRenames(ASTRewrite rewrite) {
boolean noNameChange= fFieldName.equals(fTempDeclarationNode.getName().getIdentifier());
if (fLinkedProposalModel == null && noNameChange) {
return; // no changes needed
}
TempOccurrenceAnalyzer analyzer= new TempOccurrenceAnalyzer(fTempDeclarationNode, false);
analyzer.perform();
SimpleName[] tempRefs= analyzer.getReferenceNodes(); // no javadocs (refactoring not for parameters)
for (int j= 0; j < tempRefs.length; j++) {
SimpleName occurence= tempRefs[j];
if (noNameChange) {
addLinkedName(rewrite, occurence, false);
} else {
SimpleName newName= getAST().newSimpleName(fFieldName);
addLinkedName(rewrite, newName, false);
rewrite.replace(occurence, newName, null);
}
}
}
private void addInitializersToConstructors(ASTRewrite rewrite) throws CoreException {
Assert.isTrue(! isDeclaredInAnonymousClass());
final AbstractTypeDeclaration declaration= (AbstractTypeDeclaration)getMethodDeclaration().getParent();
final MethodDeclaration[] constructors= getAllConstructors(declaration);
if (constructors.length == 0) {
AST ast= rewrite.getAST();
MethodDeclaration newConstructor= ast.newMethodDeclaration();
newConstructor.setConstructor(true);
newConstructor.modifiers().addAll(ast.newModifiers(declaration.getModifiers() & ModifierRewrite.VISIBILITY_MODIFIERS));
newConstructor.setName(ast.newSimpleName(declaration.getName().getIdentifier()));
newConstructor.setJavadoc(getNewConstructorComment(rewrite));
newConstructor.setBody(ast.newBlock());
addFieldInitializationToConstructor(rewrite, newConstructor);
int insertionIndex= computeInsertIndexForNewConstructor(declaration);
rewrite.getListRewrite(declaration, declaration.getBodyDeclarationsProperty()).insertAt(newConstructor, insertionIndex, null);
} else {
for (int index= 0; index < constructors.length; index++) {
if (shouldInsertTempInitialization(constructors[index]))
addFieldInitializationToConstructor(rewrite, constructors[index]);
}
}
}
private String getEnclosingTypeName() {
return getEnclosingType().getName().getIdentifier();
}
private AbstractTypeDeclaration getEnclosingType() {
return ASTNodes.getParent(getTempDeclarationStatement(), AbstractTypeDeclaration.class);
}
private Javadoc getNewConstructorComment(ASTRewrite rewrite) throws CoreException {
if (StubUtility.doAddComments(fCu.getJavaProject())){
String comment= CodeGeneration.getMethodComment(fCu, getEnclosingTypeName(), getEnclosingTypeName(), new String[0], new String[0], null, null, StubUtility.getLineDelimiterUsed(fCu));
if (comment != null && comment.length() > 0) {
return (Javadoc) rewrite.createStringPlaceholder(comment, ASTNode.JAVADOC);
}
}
return null;
}
private int computeInsertIndexForNewConstructor(AbstractTypeDeclaration declaration) {
List<BodyDeclaration> declarations= declaration.bodyDeclarations();
if (declarations.isEmpty())
return 0;
int index= findFirstMethodIndex(declaration);
if (index == -1)
return declarations.size();
else
return index;
}
private int findFirstMethodIndex(AbstractTypeDeclaration typeDeclaration) {
for (int i= 0, n= typeDeclaration.bodyDeclarations().size(); i < n; i++) {
if (typeDeclaration.bodyDeclarations().get(i) instanceof MethodDeclaration)
return i;
}
return -1;
}
private void addFieldInitializationToConstructor(ASTRewrite rewrite, MethodDeclaration constructor) {
if (constructor.getBody() == null)
constructor.setBody(getAST().newBlock());
Statement newStatement= createNewAssignmentStatement(rewrite);
rewrite.getListRewrite(constructor.getBody(), Block.STATEMENTS_PROPERTY).insertLast(newStatement, null);
}
private static boolean shouldInsertTempInitialization(MethodDeclaration constructor){
Assert.isTrue(constructor.isConstructor());
if (constructor.getBody() == null)
return false;
List<Statement> statements= constructor.getBody().statements();
if (statements == null)
return false;
if (statements.size() > 0 && statements.get(0) instanceof ConstructorInvocation)
return false;
return true;
}
private static MethodDeclaration[] getAllConstructors(AbstractTypeDeclaration typeDeclaration) {
if (typeDeclaration instanceof TypeDeclaration) {
MethodDeclaration[] allMethods= ((TypeDeclaration) typeDeclaration).getMethods();
List<MethodDeclaration> result= new ArrayList<>(Math.min(allMethods.length, 1));
for (int i= 0; i < allMethods.length; i++) {
MethodDeclaration declaration= allMethods[i];
if (declaration.isConstructor())
result.add(declaration);
}
return result.toArray(new MethodDeclaration[result.size()]);
}
return new MethodDeclaration[] {};
}
private ConvertLocalVariableDescriptor getRefactoringDescriptor() {
final Map<String, String> arguments= new HashMap<>();
String project= null;
IJavaProject javaProject= fCu.getJavaProject();
if (javaProject != null)
project= javaProject.getElementName();
final IVariableBinding binding= fTempDeclarationNode.resolveBinding();
final String description= Messages.format(RefactoringCoreMessages.PromoteTempToFieldRefactoring_descriptor_description_short, BasicElementLabels.getJavaElementName(binding.getName()));
final String header= Messages.format(RefactoringCoreMessages.PromoteTempToFieldRefactoring_descriptor_description, new String[] { BindingLabelProviderCore.getBindingLabel(binding, JavaElementLabelsCore.ALL_FULLY_QUALIFIED), BindingLabelProviderCore.getBindingLabel(binding.getDeclaringMethod(), JavaElementLabelsCore.ALL_FULLY_QUALIFIED)});
final JDTRefactoringDescriptorComment comment= new JDTRefactoringDescriptorComment(project, this, header);
comment.addSetting(Messages.format(RefactoringCoreMessages.PromoteTempToFieldRefactoring_original_pattern, BindingLabelProviderCore.getBindingLabel(binding, JavaElementLabelsCore.ALL_FULLY_QUALIFIED)));
comment.addSetting(Messages.format(RefactoringCoreMessages.PromoteTempToFieldRefactoring_field_pattern, BasicElementLabels.getJavaElementName(fFieldName)));
switch (fInitializeIn) {
case INITIALIZE_IN_CONSTRUCTOR:
comment.addSetting(RefactoringCoreMessages.PromoteTempToFieldRefactoring_initialize_constructor);
break;
case INITIALIZE_IN_FIELD:
comment.addSetting(RefactoringCoreMessages.PromoteTempToFieldRefactoring_initialize_declaration);
break;
case INITIALIZE_IN_METHOD:
comment.addSetting(RefactoringCoreMessages.PromoteTempToFieldRefactoring_initialize_method);
break;
}
String visibility= JdtFlags.getVisibilityString(fVisibility);
if ("".equals(visibility)) //$NON-NLS-1$
visibility= RefactoringCoreMessages.PromoteTempToFieldRefactoring_default_visibility;
comment.addSetting(Messages.format(RefactoringCoreMessages.PromoteTempToFieldRefactoring_visibility_pattern, visibility));
if (fDeclareFinal && fDeclareStatic)
comment.addSetting(RefactoringCoreMessages.PromoteTempToFieldRefactoring_declare_final_static);
else if (fDeclareFinal)
comment.addSetting(RefactoringCoreMessages.PromoteTempToFieldRefactoring_declare_final);
else if (fDeclareStatic)
comment.addSetting(RefactoringCoreMessages.PromoteTempToFieldRefactoring_declare_static);
final ConvertLocalVariableDescriptor descriptor= RefactoringSignatureDescriptorFactory.createConvertLocalVariableDescriptor(project, description, comment.asString(), arguments, RefactoringDescriptor.STRUCTURAL_CHANGE);
arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT, JavaRefactoringDescriptorUtil.elementToHandle(project, fCu));
arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME, fFieldName);
arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION, Integer.valueOf(fSelectionStart).toString() + " " + Integer.valueOf(fSelectionLength).toString()); //$NON-NLS-1$
arguments.put(ATTRIBUTE_STATIC, Boolean.valueOf(fDeclareStatic).toString());
arguments.put(ATTRIBUTE_FINAL, Boolean.valueOf(fDeclareFinal).toString());
arguments.put(ATTRIBUTE_VISIBILITY, Integer.valueOf(fVisibility).toString());
arguments.put(ATTRIBUTE_INITIALIZE, Integer.valueOf(fInitializeIn).toString());
return descriptor;
}
private void addLocalDeclarationSplit(ASTRewrite rewrite) {
VariableDeclarationStatement tempDeclarationStatement= getTempDeclarationStatement();
ASTNode parentStatement= tempDeclarationStatement.getParent();
ListRewrite listRewrite;
if (parentStatement instanceof SwitchStatement) {
listRewrite= rewrite.getListRewrite(parentStatement, SwitchStatement.STATEMENTS_PROPERTY);
} else if (parentStatement instanceof Block) {
listRewrite= rewrite.getListRewrite(parentStatement, Block.STATEMENTS_PROPERTY);
} else {
// should not happen. VariableDeclaration's can not be in a control statement body
listRewrite= null;
Assert.isTrue(false);
}
int statementIndex= listRewrite.getOriginalList().indexOf(tempDeclarationStatement);
Assert.isTrue(statementIndex != -1);
Statement newStatement= createNewAssignmentStatement(rewrite);
List<VariableDeclarationFragment> fragments= tempDeclarationStatement.fragments();
int fragmentIndex= fragments.indexOf(fTempDeclarationNode);
Assert.isTrue(fragmentIndex != -1);
if (fragments.size() == 1) {
rewrite.replace(tempDeclarationStatement, newStatement, null);
return;
}
for (int i1= fragmentIndex, n = fragments.size(); i1 < n; i1++) {
VariableDeclarationFragment fragment= fragments.get(i1);
rewrite.remove(fragment, null);
}
if (fragmentIndex == 0)
rewrite.remove(tempDeclarationStatement, null);
Assert.isTrue(tempHasInitializer());
listRewrite.insertAt(newStatement, statementIndex + 1, null);
if (fragmentIndex + 1 < fragments.size()){
VariableDeclarationFragment firstFragmentAfter= fragments.get(fragmentIndex + 1);
VariableDeclarationFragment copyfirstFragmentAfter= (VariableDeclarationFragment)rewrite.createCopyTarget(firstFragmentAfter);
VariableDeclarationStatement statement= getAST().newVariableDeclarationStatement(copyfirstFragmentAfter);
Type type= (Type)rewrite.createCopyTarget(tempDeclarationStatement.getType());
statement.setType(type);
List<IExtendedModifier> modifiers= tempDeclarationStatement.modifiers();
if (modifiers.size() > 0) {
ListRewrite modifiersRewrite= rewrite.getListRewrite(tempDeclarationStatement, VariableDeclarationStatement.MODIFIERS2_PROPERTY);
ASTNode firstModifier= (ASTNode) modifiers.get(0);
ASTNode lastModifier= (ASTNode) modifiers.get(modifiers.size() - 1);
ASTNode modifiersCopy= modifiersRewrite.createCopyTarget(firstModifier, lastModifier);
statement.modifiers().add(modifiersCopy);
}
for (int i= fragmentIndex + 2; i < fragments.size(); i++) {
VariableDeclarationFragment fragment= fragments.get(i);
VariableDeclarationFragment fragmentCopy= (VariableDeclarationFragment)rewrite.createCopyTarget(fragment);
statement.fragments().add(fragmentCopy);
}
listRewrite.insertAt(statement, statementIndex + 2, null);
}
}
private Statement createNewAssignmentStatement(ASTRewrite rewrite) {
AST ast= getAST();
Assignment assignment= ast.newAssignment();
SimpleName fieldName= ast.newSimpleName(fFieldName);
addLinkedName(rewrite, fieldName, true);
assignment.setLeftHandSide(fieldName);
assignment.setRightHandSide(getTempInitializerCopy(rewrite));
return ast.newExpressionStatement(assignment);
}
private void addLinkedName(ASTRewrite rewrite, SimpleName fieldName, boolean isFirst) {
if (fLinkedProposalModel != null) {
fLinkedProposalModel.getPositionGroup(LINKED_NAME, true).addPosition(rewrite.track(fieldName), isFirst);
}
}
private Expression getTempInitializerCopy(ASTRewrite rewrite) {
final Expression initializer= (Expression) rewrite.createCopyTarget(getTempInitializer());
if (initializer instanceof ArrayInitializer && ASTNodes.getDimensions(fTempDeclarationNode) > 0) {
ArrayCreation arrayCreation= rewrite.getAST().newArrayCreation();
arrayCreation.setType((ArrayType) ASTNodeFactory.newType(rewrite.getAST(), fTempDeclarationNode));
arrayCreation.setInitializer((ArrayInitializer) initializer);
return arrayCreation;
}
return initializer;
}
private void addLocalDeclarationRemoval(ASTRewrite rewrite) {
VariableDeclarationStatement tempDeclarationStatement= getTempDeclarationStatement();
List<VariableDeclarationFragment> fragments= tempDeclarationStatement.fragments();
int fragmentIndex= fragments.indexOf(fTempDeclarationNode);
Assert.isTrue(fragmentIndex != -1);
VariableDeclarationFragment fragment= fragments.get(fragmentIndex);
rewrite.remove(fragment, null);
if (fragments.size() == 1)
rewrite.remove(tempDeclarationStatement, null);
}
private void addFieldDeclaration(ASTRewrite rewrite) {
FieldDeclaration[] fields= getFieldDeclarations();
ASTNode parent= getMethodDeclaration().getParent();
ChildListPropertyDescriptor descriptor= ASTNodes.getBodyDeclarationsProperty(parent);
int insertIndex;
if (fields.length == 0)
insertIndex= 0;
else
insertIndex= ASTNodes.getBodyDeclarations(parent).indexOf(fields[fields.length - 1]) + 1;
final FieldDeclaration declaration= createNewFieldDeclaration(rewrite);
rewrite.getListRewrite(parent, descriptor).insertAt(declaration, insertIndex, null);
}
private FieldDeclaration createNewFieldDeclaration(ASTRewrite rewrite) {
AST ast= getAST();
VariableDeclarationFragment fragment= ast.newVariableDeclarationFragment();
SimpleName variableName= ast.newSimpleName(fFieldName);
fragment.setName(variableName);
addLinkedName(rewrite, variableName, false);
List<Dimension> extraDimensions= DimensionRewrite.copyDimensions(fTempDeclarationNode.extraDimensions(), rewrite);
fragment.extraDimensions().addAll(extraDimensions);
if (fInitializeIn == INITIALIZE_IN_FIELD && tempHasInitializer()){
Expression initializer= (Expression)rewrite.createCopyTarget(getTempInitializer());
fragment.setInitializer(initializer);
}
FieldDeclaration fieldDeclaration= ast.newFieldDeclaration(fragment);
VariableDeclarationStatement vds= getTempDeclarationStatement();
ITypeBinding binding= vds.getType().resolveBinding();
Type type;
// "var" type cannot be initialized separately, so change to inferred type
if (ASTNodes.isVarType(vds, fCompilationUnitNode) && binding != null) {
ImportRewrite importRewrite= StubUtility.createImportRewrite(fCompilationUnitNode, true);
ImportRewriteContext context= new ContextSensitiveImportRewriteContext(fCompilationUnitNode, importRewrite);
type= importRewrite.addImport(binding, getAST(), context, TypeLocation.FIELD);
} else {
type= (Type)rewrite.createCopyTarget(vds.getType());
}
fieldDeclaration.setType(type);
fieldDeclaration.modifiers().addAll(ASTNodeFactory.newModifiers(ast, getModifiers()));
return fieldDeclaration;
}
private int getModifiers() {
int flags= fVisibility;
if (fDeclareFinal)
flags |= Modifier.FINAL;
if (fDeclareStatic)
flags |= Modifier.STATIC;
return flags;
}
private AST getAST(){
return fTempDeclarationNode.getAST();
}
private static class LocalTypeAndVariableUsageAnalyzer extends HierarchicalASTVisitor{
private final List<IBinding> fLocalDefinitions= new ArrayList<>(0); // List of IBinding (Variable and Type)
private final List<SimpleName> fLocalReferencesToEnclosing= new ArrayList<>(0); // List of ASTNodes
private final List<ITypeBinding> fMethodTypeVariables;
private boolean fClassTypeVariablesUsed= false;
public LocalTypeAndVariableUsageAnalyzer(ITypeBinding[] methodTypeVariables) {
fMethodTypeVariables= Arrays.asList(methodTypeVariables);
}
public List<SimpleName> getUsageOfEnclosingNodes(){
return fLocalReferencesToEnclosing;
}
public boolean getClassTypeVariablesUsed() {
return fClassTypeVariablesUsed;
}
@Override
public boolean visit(SimpleName node) {
ITypeBinding typeBinding= node.resolveTypeBinding();
if (typeBinding != null && typeBinding.isLocal()) {
if (node.isDeclaration()) {
fLocalDefinitions.add(typeBinding);
} else if (! fLocalDefinitions.contains(typeBinding)) {
fLocalReferencesToEnclosing.add(node);
}
}
if (typeBinding != null && typeBinding.isTypeVariable()) {
if (node.isDeclaration()) {
fLocalDefinitions.add(typeBinding);
} else if (! fLocalDefinitions.contains(typeBinding)) {
if (fMethodTypeVariables.contains(typeBinding)) {
fLocalReferencesToEnclosing.add(node);
} else {
fClassTypeVariablesUsed= true;
}
}
}
IBinding binding= node.resolveBinding();
if (binding != null && binding.getKind() == IBinding.VARIABLE && ! ((IVariableBinding)binding).isField()) {
if (node.isDeclaration()) {
fLocalDefinitions.add(binding);
} else if (! fLocalDefinitions.contains(binding)) {
fLocalReferencesToEnclosing.add(node);
}
}
return super.visit(node);
}
}
private RefactoringStatus initialize(JavaRefactoringArguments arguments) {
fSelfInitializing= true;
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.CONVERT_LOCAL_VARIABLE);
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= flag;
}
final String initialize= arguments.getAttribute(ATTRIBUTE_INITIALIZE);
if (initialize != null && !"".equals(initialize)) {//$NON-NLS-1$
int value= 0;
try {
value= Integer.parseInt(initialize);
} catch (NumberFormatException exception) {
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_INITIALIZE));
}
fInitializeIn= value;
}
final String name= arguments.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME);
if (name != null && !"".equals(name)) //$NON-NLS-1$
fFieldName= name;
else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME));
final String declareStatic= arguments.getAttribute(ATTRIBUTE_STATIC);
if (declareStatic != null) {
fDeclareStatic= Boolean.valueOf(declareStatic).booleanValue();
} else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_STATIC));
final String declareFinal= arguments.getAttribute(ATTRIBUTE_FINAL);
if (declareFinal != null) {
fDeclareFinal= Boolean.valueOf(declareFinal).booleanValue();
} else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_FINAL));
return new RefactoringStatus();
}
public void setLinkedProposalModel(LinkedProposalModelCore model) {
fLinkedProposalModel= model;
}
public Map<String, String> getFormatterOptions() {
return fFormatterOptions;
}
/**
* Set the formatter options to format the refactored code.
* @param formatterOptions the formatter options to format the refactored code
*/
public void setFormatterOptions(Map<String, String> formatterOptions) {
fFormatterOptions= formatterOptions;
}
}