/******************************************************************************* * 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 * * Referenced the origin implementation: * org.eclipse.jdt.internal.corext.refactoring.code.ExtractTempRefactoring * org.eclipse.jdt.internal.corext.refactoring.code.PromoteTempToFieldRefactoring * * Contributors: * IBM Corporation - initial API and implementation * Microsoft Corporation - create the new refactoring implementation *******************************************************************************/ package org.eclipse.jdt.ls.core.internal.corext.refactoring.code; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.jdt.core.Flags; import org.eclipse.jdt.core.ICompilationUnit; 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.ASTVisitor; import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.AnonymousClassDeclaration; import org.eclipse.jdt.core.dom.ArrayInitializer; 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.CastExpression; import org.eclipse.jdt.core.dom.CatchClause; import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor; import org.eclipse.jdt.core.dom.ClassInstanceCreation; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.ConstructorInvocation; 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.ForStatement; import org.eclipse.jdt.core.dom.IBinding; 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.Initializer; import org.eclipse.jdt.core.dom.LambdaExpression; 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.ParenthesizedExpression; import org.eclipse.jdt.core.dom.PostfixExpression; import org.eclipse.jdt.core.dom.PrefixExpression; import org.eclipse.jdt.core.dom.QualifiedName; import org.eclipse.jdt.core.dom.ReturnStatement; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.Statement; import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor; import org.eclipse.jdt.core.dom.SuperConstructorInvocation; import org.eclipse.jdt.core.dom.SwitchCase; import org.eclipse.jdt.core.dom.SwitchStatement; import org.eclipse.jdt.core.dom.ThisExpression; import org.eclipse.jdt.core.dom.TryStatement; import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.dom.VariableDeclarationExpression; 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.internal.core.manipulation.StubUtility; 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.Bindings; import org.eclipse.jdt.internal.corext.dom.HierarchicalASTVisitor; import org.eclipse.jdt.internal.corext.dom.ScopeAnalyzer; 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.structure.CompilationUnitRewrite; import org.eclipse.jdt.internal.corext.refactoring.util.JavaStatusContext; import org.eclipse.jdt.internal.corext.refactoring.util.RefactoringASTParser; import org.eclipse.jdt.ls.core.internal.IConstants; import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin; import org.eclipse.jdt.ls.core.internal.corext.dom.ModifierRewrite; import org.eclipse.jdt.ls.core.internal.corext.dom.fragments.ASTFragmentFactory; import org.eclipse.jdt.ls.core.internal.corext.dom.fragments.IASTFragment; import org.eclipse.jdt.ls.core.internal.corext.dom.fragments.IExpressionFragment; import org.eclipse.jdt.ls.core.internal.corext.refactoring.RefactoringCoreMessages; import org.eclipse.jdt.ls.core.internal.corext.refactoring.base.RefactoringStatusCodes; import org.eclipse.jdt.ls.core.internal.corext.refactoring.util.NoCommentSourceRangeComputer; import org.eclipse.jdt.ls.core.internal.corext.refactoring.util.ResourceUtil; import org.eclipse.jdt.ls.core.internal.corrections.ASTResolving; import org.eclipse.ltk.core.refactoring.Change; import org.eclipse.ltk.core.refactoring.Refactoring; import org.eclipse.ltk.core.refactoring.RefactoringStatus; import org.eclipse.text.edits.TextEditGroup; public class ExtractFieldRefactoring extends Refactoring { 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 int fSelectionStart; private int fSelectionLength; private ICompilationUnit fCu; private CompilationUnit fCompilationUnitNode; private String[] fGuessedFieldNames; private LinkedProposalModelCore fLinkedProposalModel; private IExpressionFragment fSelectedExpression; private String[] fExcludedVariableNames; private String[] fExcludedFieldNames; private CompilationUnitRewrite fCURewrite; private boolean fDeclareFinal; private String fFieldName; private int fVisibility; private int fInitializeIn; private Map fFormatterOptions; private boolean fInitializerUsesLocalTypes; private boolean fDeclareStatic; private static final String KEY_NAME = "name"; //$NON-NLS-1$ private static final String KEY_TYPE = "type"; //$NON-NLS-1$ /** * Creates a new extract field refactoring * * @param unit * the compilation unit, or <code>null</code> if invoked by scripting * @param selectionStart * start of selection * @param selectionLength * length of selection */ public ExtractFieldRefactoring(ICompilationUnit unit, int selectionStart, int selectionLength) { Assert.isTrue(selectionStart >= 0); Assert.isTrue(selectionLength >= 0); fSelectionStart = selectionStart; fSelectionLength = selectionLength; fCu = unit; fCompilationUnitNode = null; fFieldName = ""; //$NON-NLS-1$ fLinkedProposalModel = null; fVisibility = Modifier.PRIVATE; fDeclareFinal = false; fDeclareStatic = false; fInitializeIn = INITIALIZE_IN_METHOD; } public ExtractFieldRefactoring(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(); fCompilationUnitNode = astRoot; fDeclareFinal = false; fDeclareStatic = false; fFieldName = ""; //$NON-NLS-1$ fLinkedProposalModel = null; fVisibility = Modifier.PRIVATE; fInitializeIn = INITIALIZE_IN_METHOD; } @Override public String getName() { return RefactoringCoreMessages.ExtractFieldRefactoring_name; } public int getVisibility() { return fVisibility; } public boolean getDeclareFinal() { return fDeclareFinal; } public int getInitializeIn() { return fInitializeIn; } public Map<String, String> getFormatterOptions() { return fFormatterOptions; } public void setInitializeIn(int initializeIn) { Assert.isTrue(initializeIn == INITIALIZE_IN_CONSTRUCTOR || initializeIn == INITIALIZE_IN_FIELD || initializeIn == INITIALIZE_IN_METHOD); fInitializeIn = initializeIn; } /** * 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; } public boolean canEnableSettingDeclareInConstructors() throws JavaModelException { return !fDeclareStatic && !fInitializerUsesLocalTypes && !getMethodDeclaration().isConstructor() && !isDeclaredInAnonymousClass() && !isDeclaredInStaticMethod(); } public boolean canEnableSettingDeclareInMethod() { return !fDeclareFinal; } public boolean canEnableSettingDeclareInFieldDeclaration() { return !fInitializerUsesLocalTypes; } @Override public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException, OperationCanceledException { try { pm.beginTask("", 16); //$NON-NLS-1$ RefactoringStatus result = Checks.validateModifiesFiles(ResourceUtil.getFiles(new ICompilationUnit[] { fCu }), getValidationContext(), pm); if (result.hasFatalError()) { return result; } if (fCompilationUnitNode == null) { fCompilationUnitNode = RefactoringASTParser.parseWithASTProvider(fCu, true, new SubProgressMonitor(pm, 3)); } pm.worked(1); if (fCURewrite == null) { fCURewrite = new CompilationUnitRewrite(fCu, fCompilationUnitNode); fCURewrite.setFormattingOptions(fFormatterOptions); fCURewrite.getASTRewrite().setTargetSourceRangeComputer(new NoCommentSourceRangeComputer()); } pm.worked(1); // Check the conditions for extracting an expression to a variable. IExpressionFragment selectedExpression = getSelectedExpression(); if (selectedExpression == null) { String message = RefactoringCoreMessages.ExtractTempRefactoring_select_expression; return CodeRefactoringUtil.checkMethodSyntaxErrors(fSelectionStart, fSelectionLength, fCompilationUnitNode, message); } pm.worked(1); if (isUsedInExplicitConstructorCall()) { return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractTempRefactoring_explicit_constructor); } pm.worked(1); ASTNode associatedNode = selectedExpression.getAssociatedNode(); if (getEnclosingBodyNode() == null || ASTNodes.getParent(associatedNode, Annotation.class) != null) { return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractTempRefactoring_expr_in_method_or_initializer); } pm.worked(1); if (associatedNode instanceof Name && associatedNode.getParent() instanceof ClassInstanceCreation && associatedNode.getLocationInParent() == ClassInstanceCreation.TYPE_PROPERTY) { return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractTempRefactoring_name_in_new); } pm.worked(1); result.merge(checkExpression()); if (result.hasFatalError()) { return result; } pm.worked(1); result.merge(checkExpressionFragmentIsRValue()); if (result.hasFatalError()) { return result; } pm.worked(1); Expression associatedExpression = selectedExpression.getAssociatedExpression(); if (isUsedInForInitializerOrUpdater(associatedExpression)) { return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractTempRefactoring_for_initializer_updater); } pm.worked(1); if (isReferringToLocalVariableFromFor(associatedExpression)) { return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractTempRefactoring_refers_to_for_variable); } pm.worked(1); // Check the conditions for extracting an expression to field. ASTNode declaringType = getEnclosingTypeDeclaration(); if (declaringType instanceof TypeDeclaration && ((TypeDeclaration) declaringType).isInterface()) { return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractFieldRefactoring_interface_methods); } pm.worked(1); result.merge(checkTempTypeForLocalTypeUsage()); if (result.hasFatalError()) { return result; } pm.worked(1); checkTempInitializerForLocalTypeUsage(); initializeDefaults(); pm.worked(1); return result; } finally { pm.done(); } } @Override public RefactoringStatus checkFinalConditions(IProgressMonitor pm) throws CoreException, OperationCanceledException { try { pm.beginTask(RefactoringCoreMessages.ExtractTempRefactoring_checking_preconditions, 4); RefactoringStatus result = new RefactoringStatus(); result.merge(checkMatchingFragments()); return result; } finally { pm.done(); } } @Override public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException { try { pm.beginTask(RefactoringCoreMessages.ExtractFieldRefactoring_creating_change, 1); try { if (fInitializeIn == INITIALIZE_IN_METHOD) { addInitializerToMethod(); } else if (fInitializeIn == INITIALIZE_IN_CONSTRUCTOR) { addInitializersToConstructors(fCURewrite.getASTRewrite()); } addFieldDeclaration(); addReplaceExpressionWithField(); } catch (CoreException exception) { JavaLanguageServerPlugin.logException("Problem with extract temp filed ", exception); } return fCURewrite.createChange(RefactoringCoreMessages.ExtractFieldRefactoring_name, true, new SubProgressMonitor(pm, 1)); } finally { pm.done(); } } private void initializeDefaults() throws JavaModelException { fVisibility = Modifier.PRIVATE; fDeclareStatic = isDeclaredInStaticMethod(); fDeclareFinal = false; if (canEnableSettingDeclareInMethod()) { fInitializeIn = INITIALIZE_IN_METHOD; } else if (canEnableSettingDeclareInFieldDeclaration()) { fInitializeIn = INITIALIZE_IN_FIELD; } else if (canEnableSettingDeclareInConstructors()) { fInitializeIn = INITIALIZE_IN_CONSTRUCTOR; } } private IExpressionFragment getSelectedExpression() throws JavaModelException { if (fSelectedExpression != null) { return fSelectedExpression; } IASTFragment selectedFragment = ASTFragmentFactory.createFragmentForSourceRange(new SourceRange(fSelectionStart, fSelectionLength), fCompilationUnitNode, fCu); if (selectedFragment instanceof IExpressionFragment && !Checks.isInsideJavadoc(selectedFragment.getAssociatedNode())) { fSelectedExpression = (IExpressionFragment) selectedFragment; } else if (selectedFragment != null) { if (selectedFragment.getAssociatedNode() instanceof ExpressionStatement) { ExpressionStatement exprStatement = (ExpressionStatement) selectedFragment.getAssociatedNode(); Expression expression = exprStatement.getExpression(); fSelectedExpression = (IExpressionFragment) ASTFragmentFactory.createFragmentForFullSubtree(expression); } else if (selectedFragment.getAssociatedNode() instanceof Assignment) { Assignment assignment = (Assignment) selectedFragment.getAssociatedNode(); fSelectedExpression = (IExpressionFragment) ASTFragmentFactory.createFragmentForFullSubtree(assignment); } } if (fSelectedExpression != null && Checks.isEnumCase(fSelectedExpression.getAssociatedExpression().getParent())) { fSelectedExpression = null; } return fSelectedExpression; } private boolean isUsedInExplicitConstructorCall() throws JavaModelException { Expression selectedExpression = getSelectedExpression().getAssociatedExpression(); if (ASTNodes.getParent(selectedExpression, ConstructorInvocation.class) != null) { return true; } if (ASTNodes.getParent(selectedExpression, SuperConstructorInvocation.class) != null) { return true; } return false; } private ASTNode getEnclosingBodyNode() throws JavaModelException { ASTNode node = getSelectedExpression().getAssociatedNode(); // expression must be in a method, lambda or initializer body. // make sure it is not in method or parameter annotation StructuralPropertyDescriptor location = null; while (node != null && !(node instanceof BodyDeclaration)) { location = node.getLocationInParent(); node = node.getParent(); if (node instanceof LambdaExpression) { break; } } if (location == MethodDeclaration.BODY_PROPERTY || location == Initializer.BODY_PROPERTY || (location == LambdaExpression.BODY_PROPERTY && ((LambdaExpression) node).resolveMethodBinding() != null)) { return (ASTNode) node.getStructuralProperty(location); } return null; } private RefactoringStatus checkExpression() throws JavaModelException { Expression selectedExpression = getSelectedExpression().getAssociatedExpression(); if (selectedExpression != null) { final ASTNode parent = selectedExpression.getParent(); if (selectedExpression instanceof NullLiteral) { return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractTempRefactoring_null_literals); } else if (selectedExpression instanceof ArrayInitializer) { return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractTempRefactoring_array_initializer); } else if (selectedExpression instanceof Assignment) { if (parent instanceof Expression && !(parent instanceof ParenthesizedExpression)) { return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractTempRefactoring_assignment); } else { return null; } } else if (selectedExpression instanceof SimpleName) { if ((((SimpleName) selectedExpression)).isDeclaration()) { return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractTempRefactoring_names_in_declarations); } if (parent instanceof QualifiedName && selectedExpression.getLocationInParent() == QualifiedName.NAME_PROPERTY || parent instanceof FieldAccess && selectedExpression.getLocationInParent() == FieldAccess.NAME_PROPERTY) { return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractTempRefactoring_select_expression); } } else if (selectedExpression instanceof VariableDeclarationExpression && parent instanceof TryStatement) { return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractTempRefactoring_resource_in_try_with_resources); } } return null; } // !! Same as in ExtractConstantRefactoring private RefactoringStatus checkExpressionFragmentIsRValue() throws JavaModelException { switch (Checks.checkExpressionIsRValue(getSelectedExpression().getAssociatedExpression())) { case Checks.NOT_RVALUE_MISC: return RefactoringStatus.createStatus(RefactoringStatus.FATAL, RefactoringCoreMessages.ExtractTempRefactoring_select_expression, null, IConstants.PLUGIN_ID, RefactoringStatusCodes.EXPRESSION_NOT_RVALUE, null); case Checks.NOT_RVALUE_VOID: return RefactoringStatus.createStatus(RefactoringStatus.FATAL, RefactoringCoreMessages.ExtractTempRefactoring_no_void, null, IConstants.PLUGIN_ID, 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 static boolean isUsedInForInitializerOrUpdater(Expression expression) { ASTNode parent = expression.getParent(); if (parent instanceof ForStatement) { ForStatement forStmt = (ForStatement) parent; return forStmt.initializers().contains(expression) || forStmt.updaters().contains(expression); } return false; } private static boolean isReferringToLocalVariableFromFor(Expression expression) { ASTNode current = expression; ASTNode parent = current.getParent(); while (parent != null && !(parent instanceof BodyDeclaration)) { if (parent instanceof ForStatement) { ForStatement forStmt = (ForStatement) parent; if (forStmt.initializers().contains(current) || forStmt.updaters().contains(current) || forStmt.getExpression() == current) { List<Expression> initializers = forStmt.initializers(); if (initializers.size() == 1 && initializers.get(0) instanceof VariableDeclarationExpression) { List<IVariableBinding> forInitializerVariables = getForInitializedVariables((VariableDeclarationExpression) initializers.get(0)); ForStatementChecker checker = new ForStatementChecker(forInitializerVariables); expression.accept(checker); if (checker.isReferringToForVariable()) { return true; } } } } current = parent; parent = current.getParent(); } return false; } private static List<IVariableBinding> getForInitializedVariables(VariableDeclarationExpression variableDeclarations) { List<IVariableBinding> forInitializerVariables = new ArrayList<>(1); for (Iterator<VariableDeclarationFragment> iter = variableDeclarations.fragments().iterator(); iter.hasNext();) { VariableDeclarationFragment fragment = iter.next(); IVariableBinding binding = fragment.resolveBinding(); if (binding != null) { forInitializerVariables.add(binding); } } return forInitializerVariables; } private RefactoringStatus checkTempTypeForLocalTypeUsage() throws JavaModelException { Expression expression = getSelectedExpression().getAssociatedExpression(); Type resultingType = null; ITypeBinding typeBinding = expression.resolveTypeBinding(); AST ast = fCURewrite.getAST(); if (expression instanceof ClassInstanceCreation && (typeBinding == null || typeBinding.getTypeArguments().length == 0)) { resultingType = ((ClassInstanceCreation) expression).getType(); } else if (expression instanceof CastExpression) { resultingType = ((CastExpression) expression).getType(); } else { if (typeBinding == null) { typeBinding = ASTResolving.guessBindingForReference(expression); } if (typeBinding != null) { typeBinding = Bindings.normalizeForDeclarationUse(typeBinding, ast); ImportRewrite importRewrite = fCURewrite.getImportRewrite(); ImportRewriteContext context = new ContextSensitiveImportRewriteContext(expression, importRewrite); resultingType = importRewrite.addImport(typeBinding, ast, context, TypeLocation.LOCAL_VARIABLE); } else { resultingType = ast.newSimpleType(ast.newSimpleName("Object")); //$NON-NLS-1$ } } IMethodBinding declaringMethodBinding = getMethodDeclaration().resolveBinding(); ITypeBinding[] methodTypeParameters = declaringMethodBinding == null ? new ITypeBinding[0] : declaringMethodBinding.getTypeParameters(); LocalTypeAndVariableUsageAnalyzer analyzer = new LocalTypeAndVariableUsageAnalyzer(methodTypeParameters); resultingType.accept(analyzer); boolean usesLocalTypes = !analyzer.getUsageOfEnclosingNodes().isEmpty(); if (usesLocalTypes) { return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractFieldRefactoring_uses_type_declared_locally); } return null; } private void checkTempInitializerForLocalTypeUsage() throws JavaModelException { Expression initializer = getSelectedExpression().getAssociatedExpression(); 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(); } public void setLinkedProposalModel(LinkedProposalModelCore linkedProposalModel) { fLinkedProposalModel = linkedProposalModel; } public String guessFieldName() { String[] proposals = guessFieldNames(); if (proposals.length == 0) { return fFieldName; } else { return proposals[0]; } } /** * @return proposed field names (may be empty, but not null). The first proposal * should be used as "best guess" (if it exists). */ public String[] guessFieldNames() { if (fGuessedFieldNames == null) { try { Expression expression = getSelectedExpression().getAssociatedExpression(); if (expression != null) { ITypeBinding binding = guessBindingForReference(expression); int modifiers = getModifiers(); int variableKind; if (Flags.isFinal(modifiers) && Flags.isStatic(modifiers)) { variableKind = NamingConventions.VK_STATIC_FINAL_FIELD; } else if (Flags.isStatic(modifiers)) { variableKind = NamingConventions.VK_STATIC_FIELD; } else { variableKind = NamingConventions.VK_INSTANCE_FIELD; } fGuessedFieldNames = StubUtility.getVariableNameSuggestions(variableKind, fCu.getJavaProject(), binding, expression, Arrays.asList(getExcludedFieldNames())); } } catch (JavaModelException e) { } if (fGuessedFieldNames == null) { fGuessedFieldNames = new String[0]; } } return fGuessedFieldNames; } private ITypeBinding guessBindingForReference(Expression expression) { ITypeBinding binding = expression.resolveTypeBinding(); if (binding == null) { binding = ASTResolving.guessBindingForReference(expression); } return binding; } private String[] getExcludedVariableNames() { if (fExcludedVariableNames == null) { List<String> excludedNames = new ArrayList<>(); try { IBinding[] bindings = new ScopeAnalyzer(fCompilationUnitNode).getDeclarationsInScope(getSelectedExpression().getStartPosition(), ScopeAnalyzer.VARIABLES | ScopeAnalyzer.CHECK_VISIBILITY); for (int i = 0; i < bindings.length; i++) { excludedNames.add(bindings[i].getName()); } } catch (JavaModelException e) { // do nothing. } fExcludedVariableNames = excludedNames.toArray(new String[0]); } return fExcludedVariableNames; } private String[] getExcludedFieldNames() { if (fExcludedFieldNames == null) { List<String> result = new ArrayList<>(); try { final ASTNode type = getEnclosingTypeDeclaration(); if (type instanceof TypeDeclaration) { FieldDeclaration[] fields = ((TypeDeclaration) type).getFields(); 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()); } } } } catch (JavaModelException e) { // do nothing. } fExcludedFieldNames = result.toArray(new String[result.size()]); } return fExcludedFieldNames; } public void setFieldName(String guessFieldName) { fFieldName = guessFieldName; } private boolean isStandaloneExpression() throws JavaModelException { IExpressionFragment selectedFragment = getSelectedExpression(); ASTNode target = selectedFragment.getAssociatedNode(); ASTNode parent = target.getParent(); return (parent instanceof ExpressionStatement || parent instanceof LambdaExpression) && selectedFragment.matches(ASTFragmentFactory.createFragmentForFullSubtree(target)); } private void addInitializerToMethod() throws CoreException { Statement vds = createNewAssignmentStatement(); IExpressionFragment selectedFragment = getSelectedExpression(); Expression selectedExpression = selectedFragment.getAssociatedExpression(); ASTNode target = selectedFragment.getAssociatedNode(); ASTRewrite rewrite = fCURewrite.getASTRewrite(); AST ast = fCURewrite.getAST(); TextEditGroup groupDescription = fCURewrite.createGroupDescription(RefactoringCoreMessages.ExtractFieldRefactoring_initialize_field); ASTNode parent = target.getParent(); StructuralPropertyDescriptor locationInParent = target.getLocationInParent(); if (isStandaloneExpression()) { ASTNode replacement; if (parent instanceof LambdaExpression) { Block blockBody = ast.newBlock(); blockBody.statements().add(vds); if (!Bindings.isVoidType(((LambdaExpression) parent).resolveMethodBinding().getReturnType())) { ReturnStatement returnStatement = ast.newReturnStatement(); returnStatement.setExpression(ast.newSimpleName(fFieldName)); blockBody.statements().add(returnStatement); } replacement = blockBody; } else if (ASTNodes.isControlStatementBody(parent.getLocationInParent())) { Block block = ast.newBlock(); block.statements().add(vds); replacement = block; } else { replacement = vds; } ASTNode replacee = parent instanceof LambdaExpression || !ASTNodes.hasSemicolon((ExpressionStatement) parent, fCu) ? selectedExpression : parent; rewrite.replace(replacee, replacement, groupDescription); return; } while (locationInParent != Block.STATEMENTS_PROPERTY && locationInParent != SwitchStatement.STATEMENTS_PROPERTY) { if (ASTNodes.isControlStatementBody(locationInParent)) { // create intermediate block if target was the body property of a control statement: Block replacement = rewrite.getAST().newBlock(); ListRewrite replacementRewrite = rewrite.getListRewrite(replacement, Block.STATEMENTS_PROPERTY); replacementRewrite.insertFirst(vds, null); replacementRewrite.insertLast(rewrite.createMoveTarget(target), null); rewrite.replace(target, replacement, groupDescription); return; } else if (locationInParent == LambdaExpression.BODY_PROPERTY && ((LambdaExpression) parent).getBody() instanceof Expression) { Block replacement = rewrite.getAST().newBlock(); ListRewrite replacementRewrite = rewrite.getListRewrite(replacement, Block.STATEMENTS_PROPERTY); replacementRewrite.insertFirst(vds, null); ASTNode moveTarget = rewrite.createMoveTarget(target); if (Bindings.isVoidType(((LambdaExpression) parent).resolveMethodBinding().getReturnType())) { ExpressionStatement expressionStatement = ast.newExpressionStatement((Expression) moveTarget); moveTarget = expressionStatement; } else { ReturnStatement returnStatement = ast.newReturnStatement(); returnStatement.setExpression((Expression) moveTarget); moveTarget = returnStatement; } replacementRewrite.insertLast(moveTarget, null); rewrite.replace(target, replacement, groupDescription); return; } target = parent; parent = parent.getParent(); locationInParent = target.getLocationInParent(); } ListRewrite listRewrite = rewrite.getListRewrite(parent, (ChildListPropertyDescriptor) locationInParent); listRewrite.insertBefore(vds, target, groupDescription); } 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.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 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 void addFieldInitializationToConstructor(ASTRewrite rewrite, MethodDeclaration constructor) throws JavaModelException { if (constructor.getBody() == null) { constructor.setBody(fCURewrite.getAST().newBlock()); } Statement newStatement = createNewAssignmentStatement(); rewrite.getListRewrite(constructor.getBody(), Block.STATEMENTS_PROPERTY).insertLast(newStatement, 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 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 void addFieldDeclaration() throws CoreException { FieldDeclaration[] fields = getFieldDeclarations(); ASTNode parent = getEnclosingTypeDeclaration(); ChildListPropertyDescriptor descriptor = ASTNodes.getBodyDeclarationsProperty(parent); int insertIndex; if (fields.length == 0) { insertIndex = 0; } else { insertIndex = ASTNodes.getBodyDeclarations(parent).indexOf(fields[fields.length - 1]) + 1; } ASTRewrite rewrite = fCURewrite.getASTRewrite(); final FieldDeclaration declaration = createNewFieldDeclaration(rewrite); rewrite.getListRewrite(parent, descriptor).insertAt(declaration, insertIndex, null); } private FieldDeclaration createNewFieldDeclaration(ASTRewrite rewrite) throws CoreException { AST ast = fCURewrite.getAST(); VariableDeclarationFragment fragment = ast.newVariableDeclarationFragment(); SimpleName variableName = ast.newSimpleName(fFieldName); fragment.setName(variableName); if (fLinkedProposalModel != null) { fLinkedProposalModel.getPositionGroup(KEY_NAME, true).addPosition(rewrite.track(variableName), false); } if (fInitializeIn == INITIALIZE_IN_FIELD) { Expression initializer = getSelectedExpression().createCopyTarget(fCURewrite.getASTRewrite(), true); fragment.setInitializer(initializer); } FieldDeclaration fieldDeclaration = ast.newFieldDeclaration(fragment); fieldDeclaration.setType(createFieldType()); fieldDeclaration.modifiers().addAll(ASTNodeFactory.newModifiers(ast, getModifiers())); return fieldDeclaration; } private FieldDeclaration[] getFieldDeclarations() throws JavaModelException { List<BodyDeclaration> bodyDeclarations = ASTNodes.getBodyDeclarations(getEnclosingTypeDeclaration()); 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()]); } private MethodDeclaration getMethodDeclaration() throws JavaModelException { return ASTNodes.getParent(getSelectedExpression().getAssociatedNode(), MethodDeclaration.class); } private ASTNode getEnclosingTypeDeclaration() throws JavaModelException { if (isDeclaredInLambdaExpression()) { return ASTNodes.getParent(getSelectedExpression().getAssociatedNode(), AbstractTypeDeclaration.class); } return getMethodDeclaration().getParent(); } private String getEnclosingTypeName() throws JavaModelException { ASTNode node = getEnclosingTypeDeclaration(); ITypeBinding typeBinding = ASTNodes.getEnclosingType(node); return typeBinding == null ? "" : typeBinding.getName(); } private boolean isDeclaredInLambdaExpression() throws JavaModelException { ASTNode node = getSelectedExpression().getAssociatedNode(); while (node != null && !(node instanceof BodyDeclaration)) { node = node.getParent(); if (node instanceof LambdaExpression) { return true; } } return false; } private boolean isDeclaredInAnonymousClass() throws JavaModelException { return null != ASTNodes.getParent(getSelectedExpression().getAssociatedNode(), AnonymousClassDeclaration.class); } private Statement createNewAssignmentStatement() throws JavaModelException { AST ast = fCURewrite.getAST(); Assignment assignment = ast.newAssignment(); SimpleName fieldName = ast.newSimpleName(fFieldName); ASTRewrite rewrite = fCURewrite.getASTRewrite(); if (fLinkedProposalModel != null) { fLinkedProposalModel.getPositionGroup(KEY_NAME, true).addPosition(rewrite.track(fieldName), true); } assignment.setLeftHandSide(wrapAsFieldAccessExpression(fieldName)); assignment.setRightHandSide(getSelectedExpression().createCopyTarget(rewrite, true)); return ast.newExpressionStatement(assignment); } private Expression wrapAsFieldAccessExpression(SimpleName fieldName) { AST ast = fCURewrite.getAST(); ASTRewrite rewrite = fCURewrite.getASTRewrite(); List<String> variableNames = Arrays.asList(getExcludedVariableNames()); if (variableNames.contains(fFieldName)) { int modifiers = getModifiers(); if (Flags.isStatic(modifiers)) { try { String enclosingTypeName = getEnclosingTypeName(); SimpleName typeName = ast.newSimpleName(enclosingTypeName); if (fLinkedProposalModel != null) { fLinkedProposalModel.getPositionGroup(KEY_NAME, true).addPosition(rewrite.track(typeName), false); } QualifiedName qualifiedName = ast.newQualifiedName(typeName, fieldName); return qualifiedName; } catch (JavaModelException e) { return wrapAsFieldAccess(fieldName, ast); } } else { return wrapAsFieldAccess(fieldName, ast); } } return fieldName; } private FieldAccess wrapAsFieldAccess(SimpleName fieldName, AST ast) { Name qualifierName = null; try { if (isDeclaredInLambdaExpression()) { String enclosingTypeName = getEnclosingTypeName(); qualifierName = ast.newSimpleName(enclosingTypeName); } } catch (JavaModelException e) { // do nothing. } FieldAccess fieldAccess = ast.newFieldAccess(); ThisExpression thisExpression = ast.newThisExpression(); if (qualifierName != null) { thisExpression.setQualifier(qualifierName); } fieldAccess.setExpression(thisExpression); fieldAccess.setName(fieldName); return fieldAccess; } private Type createFieldType() throws CoreException { Expression expression = getSelectedExpression().getAssociatedExpression(); Type resultingType = null; ITypeBinding typeBinding = expression.resolveTypeBinding(); ASTRewrite rewrite = fCURewrite.getASTRewrite(); AST ast = rewrite.getAST(); if (expression instanceof ClassInstanceCreation && (typeBinding == null || typeBinding.getTypeArguments().length == 0)) { resultingType = (Type) rewrite.createCopyTarget(((ClassInstanceCreation) expression).getType()); } else if (expression instanceof CastExpression) { resultingType = (Type) rewrite.createCopyTarget(((CastExpression) expression).getType()); } else { if (typeBinding == null) { typeBinding = ASTResolving.guessBindingForReference(expression); } if (typeBinding != null) { typeBinding = Bindings.normalizeForDeclarationUse(typeBinding, ast); ImportRewrite importRewrite = fCURewrite.getImportRewrite(); ImportRewriteContext context = new ContextSensitiveImportRewriteContext(expression, importRewrite); resultingType = importRewrite.addImport(typeBinding, ast, context, TypeLocation.LOCAL_VARIABLE); } else { resultingType = ast.newSimpleType(ast.newSimpleName("Object")); //$NON-NLS-1$ } } if (fLinkedProposalModel != null) { LinkedProposalPositionGroupCore typeGroup = fLinkedProposalModel.getPositionGroup(KEY_TYPE, true); typeGroup.addPosition(rewrite.track(resultingType), false); 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); } } } return resultingType; } private void addReplaceExpressionWithField() throws JavaModelException { ASTRewrite rewrite = fCURewrite.getASTRewrite(); AST ast = fCURewrite.getAST(); TextEditGroup groupDescription = fCURewrite.createGroupDescription(RefactoringCoreMessages.ExtractFieldRefactoring_initialize_field); IExpressionFragment selectedFragment = getSelectedExpression(); Expression selectedExpression = selectedFragment.getAssociatedExpression(); ASTNode target = getSelectedExpression().getAssociatedNode(); ASTNode parent = target.getParent(); if (isStandaloneExpression() && (fInitializeIn == INITIALIZE_IN_FIELD || fInitializeIn == INITIALIZE_IN_CONSTRUCTOR)) { ASTNode replacee = parent instanceof LambdaExpression || !ASTNodes.hasSemicolon((ExpressionStatement) parent, fCu) ? selectedExpression : parent; if (parent instanceof LambdaExpression || ASTNodes.isControlStatementBody(parent.getLocationInParent())) { SimpleName fieldName = ast.newSimpleName(fFieldName); ASTNode replacement = wrapAsFieldAccessExpression(fieldName); if (fLinkedProposalModel != null) { fLinkedProposalModel.getPositionGroup(KEY_NAME, true).addPosition(rewrite.track(replacement), false); } rewrite.replace(replacee, replacement, groupDescription); } else { rewrite.remove(replacee, groupDescription); } return; } IASTFragment[] fragmentsToReplace = retainOnlyReplacableMatches(getMatchingFragments()); ASTNode replacer; HashSet<IASTFragment> seen = new HashSet<>(); for (int i = 0; i < fragmentsToReplace.length; i++) { IASTFragment fragment = fragmentsToReplace[i]; if (!seen.add(fragment)) { continue; } SimpleName fieldName = ast.newSimpleName(fFieldName); replacer = wrapAsFieldAccessExpression(fieldName); TextEditGroup description = fCURewrite.createGroupDescription(RefactoringCoreMessages.ExtractTempRefactoring_replace); fragment.replace(rewrite, replacer, description); if (fLinkedProposalModel != null) { fLinkedProposalModel.getPositionGroup(KEY_NAME, true).addPosition(rewrite.track(replacer), false); } } } private static IASTFragment[] retainOnlyReplacableMatches(IASTFragment[] allMatches) { List<IASTFragment> result = new ArrayList<>(allMatches.length); for (int i = 0; i < allMatches.length; i++) { if (canReplace(allMatches[i])) { result.add(allMatches[i]); } } return result.toArray(new IASTFragment[result.size()]); } 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 (isMethodParameter(node)) { return false; } if (isThrowableInCatchBlock(node)) { return false; } if (parent instanceof ExpressionStatement) { return false; } if (parent instanceof LambdaExpression) { return false; } if (isLeftValue(node)) { return false; } if (isReferringToLocalVariableFromFor((Expression) node)) { return false; } if (isUsedInForInitializerOrUpdater((Expression) node)) { return false; } if (parent instanceof SwitchCase) { return false; } return true; } private static boolean isMethodParameter(ASTNode node) { return (node instanceof SimpleName) && (node.getParent() instanceof SingleVariableDeclaration) && (node.getParent().getParent() instanceof MethodDeclaration); } private static boolean isThrowableInCatchBlock(ASTNode node) { return (node instanceof SimpleName) && (node.getParent() instanceof SingleVariableDeclaration) && (node.getParent().getParent() instanceof CatchClause); } private static boolean isLeftValue(ASTNode node) { ASTNode parent = node.getParent(); if (parent instanceof Assignment) { Assignment assignment = (Assignment) parent; if (assignment.getLeftHandSide() == node) { return true; } } if (parent instanceof PostfixExpression) { return true; } if (parent instanceof PrefixExpression) { PrefixExpression.Operator op = ((PrefixExpression) parent).getOperator(); if (op.equals(PrefixExpression.Operator.DECREMENT)) { return true; } if (op.equals(PrefixExpression.Operator.INCREMENT)) { return true; } return false; } return false; } private IASTFragment[] getMatchingFragments() throws JavaModelException { return new IASTFragment[] { getSelectedExpression() }; } private RefactoringStatus checkMatchingFragments() throws JavaModelException { RefactoringStatus result = new RefactoringStatus(); IASTFragment[] matchingFragments = getMatchingFragments(); for (int i = 0; i < matchingFragments.length; i++) { ASTNode node = matchingFragments[i].getAssociatedNode(); if (isLeftValue(node) && !isReferringToLocalVariableFromFor((Expression) node)) { String msg = RefactoringCoreMessages.ExtractTempRefactoring_assigned_to; result.addWarning(msg, JavaStatusContext.create(fCu, node)); } } return result; } private int getModifiers() { int flags = fVisibility; if (isDeclaredInStaticMethod()) { flags |= Modifier.STATIC; } return flags; } private boolean isDeclaredInStaticMethod() { try { return Modifier.isStatic(getMethodDeclaration().getModifiers()); } catch (JavaModelException e) { // do nothing } return false; } private static final class ForStatementChecker extends ASTVisitor { private final Collection<IVariableBinding> fForInitializerVariables; private boolean fReferringToForVariable = false; public ForStatementChecker(Collection<IVariableBinding> forInitializerVariables) { Assert.isNotNull(forInitializerVariables); fForInitializerVariables = forInitializerVariables; } public boolean isReferringToForVariable() { return fReferringToForVariable; } @Override public boolean visit(SimpleName node) { IBinding binding = node.resolveBinding(); if (binding != null && fForInitializerVariables.contains(binding)) { fReferringToForVariable = true; } return false; } } 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); } } }