blob: 66529a1251e00c2e0def1669ca6df3596c00e01d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2019, 2022 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
* Red Hat Inc. - created core version based on CodeStyleFix
*******************************************************************************/
package org.eclipse.jdt.internal.corext.fix;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.text.edits.TextEditGroup;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.compiler.IProblem;
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.Assignment;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.CompilationUnit;
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.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.MethodInvocation;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.PackageDeclaration;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor;
import org.eclipse.jdt.core.dom.SuperFieldAccess;
import org.eclipse.jdt.core.dom.SwitchCase;
import org.eclipse.jdt.core.dom.ThisExpression;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
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.TypeLocation;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import org.eclipse.jdt.core.manipulation.ICleanUpFixCore;
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.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.dom.Bindings;
import org.eclipse.jdt.internal.corext.dom.GenericVisitor;
import org.eclipse.jdt.internal.corext.dom.ScopeAnalyzer;
import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
import org.eclipse.jdt.internal.corext.util.Messages;
import org.eclipse.jdt.internal.ui.text.correction.IProblemLocationCore;
import org.eclipse.jdt.internal.ui.text.correction.ProblemLocationCore;
/**
* A fix which fixes code style issues.
*/
public class CodeStyleFixCore extends CompilationUnitRewriteOperationsFixCore {
public final static class CodeStyleVisitor extends GenericVisitor {
private final List<CompilationUnitRewriteOperation> fResult;
private final ImportRewrite fImportRewrite;
private final boolean fFindUnqualifiedAccesses;
private final boolean fFindUnqualifiedStaticAccesses;
private final boolean fFindUnqualifiedMethodAccesses;
private final boolean fFindUnqualifiedStaticMethodAccesses;
public CodeStyleVisitor(CompilationUnit compilationUnit,
boolean findUnqualifiedAccesses,
boolean findUnqualifiedStaticAccesses,
boolean findUnqualifiedMethodAccesses,
boolean findUnqualifiedStaticMethodAccesses,
List<CompilationUnitRewriteOperation> resultingCollection) {
fFindUnqualifiedAccesses= findUnqualifiedAccesses;
fFindUnqualifiedStaticAccesses= findUnqualifiedStaticAccesses;
fFindUnqualifiedMethodAccesses= findUnqualifiedMethodAccesses;
fFindUnqualifiedStaticMethodAccesses= findUnqualifiedStaticMethodAccesses;
fImportRewrite= StubUtility.createImportRewrite(compilationUnit, true);
fResult= resultingCollection;
}
@Override
public boolean visit(TypeDeclaration node) {
if (!fFindUnqualifiedStaticAccesses && !fFindUnqualifiedStaticMethodAccesses
&& !fFindUnqualifiedAccesses && !fFindUnqualifiedMethodAccesses && node.isInterface())
return false;
return super.visit(node);
}
@Override
public boolean visit(QualifiedName node) {
if (fFindUnqualifiedAccesses || fFindUnqualifiedStaticAccesses) {
ASTNode simpleName= node;
while (simpleName instanceof QualifiedName) {
simpleName= ((QualifiedName) simpleName).getQualifier();
}
if (simpleName instanceof SimpleName) {
handleSimpleName((SimpleName)simpleName);
}
}
return false;
}
@Override
public boolean visit(SimpleName node) {
if (fFindUnqualifiedAccesses || fFindUnqualifiedStaticAccesses) {
handleSimpleName(node);
}
return false;
}
@Override
public boolean visit(MethodInvocation node) {
if (!fFindUnqualifiedMethodAccesses && !fFindUnqualifiedStaticMethodAccesses)
return true;
if (node.getExpression() != null)
return true;
IBinding binding= node.getName().resolveBinding();
if (!(binding instanceof IMethodBinding))
return true;
handleMethod(node.getName(), (IMethodBinding)binding);
return true;
}
private void handleSimpleName(SimpleName node) {
ASTNode firstExpression= node.getParent();
if (firstExpression instanceof FieldAccess) {
while (firstExpression instanceof FieldAccess) {
firstExpression= ((FieldAccess)firstExpression).getExpression();
}
if (!(firstExpression instanceof SimpleName))
return;
node= (SimpleName)firstExpression;
} else if (firstExpression instanceof SuperFieldAccess)
return;
StructuralPropertyDescriptor parentDescription= node.getLocationInParent();
if (parentDescription == VariableDeclarationFragment.NAME_PROPERTY || parentDescription == SwitchCase.EXPRESSION_PROPERTY || parentDescription == SwitchCase.EXPRESSIONS2_PROPERTY)
return;
IBinding binding= node.resolveBinding();
if (!(binding instanceof IVariableBinding))
return;
handleVariable(node, (IVariableBinding) binding);
}
private void handleVariable(SimpleName node, IVariableBinding varbinding) {
if (!varbinding.isField())
return;
if (varbinding.isEnumConstant())
return;
ITypeBinding declaringClass= varbinding.getDeclaringClass();
if (Modifier.isStatic(varbinding.getModifiers())) {
if (fFindUnqualifiedStaticAccesses) {
Initializer initializer= ASTNodes.getParent(node, Initializer.class);
//Do not qualify assignments to static final fields in static initializers (would result in compile error)
StructuralPropertyDescriptor parentDescription= node.getLocationInParent();
if (initializer != null && Modifier.isStatic(initializer.getModifiers())
&& Modifier.isFinal(varbinding.getModifiers()) && parentDescription == Assignment.LEFT_HAND_SIDE_PROPERTY)
return;
//Do not qualify static fields if defined inside an anonymous class
if (declaringClass.isAnonymous())
return;
fResult.add(new AddStaticQualifierOperation(declaringClass, node));
}
} else if (fFindUnqualifiedAccesses){
String qualifier= getThisExpressionQualifier(declaringClass, fImportRewrite, node);
if (qualifier == null)
return;
if (qualifier.length() == 0)
qualifier= null;
fResult.add(new AddThisQualifierOperation(qualifier, node));
}
}
private void handleMethod(SimpleName node, IMethodBinding binding) {
ITypeBinding declaringClass= binding.getDeclaringClass();
if (Modifier.isStatic(binding.getModifiers())) {
if (fFindUnqualifiedStaticMethodAccesses) {
//Do not qualify static fields if defined inside an anonymous class
if (declaringClass.isAnonymous())
return;
fResult.add(new AddStaticQualifierOperation(declaringClass, node));
}
} else {
if (fFindUnqualifiedMethodAccesses) {
String qualifier= getThisExpressionQualifier(declaringClass, fImportRewrite, node);
if (qualifier == null)
return;
if (qualifier.length() == 0)
qualifier= null;
fResult.add(new AddThisQualifierOperation(qualifier, node));
}
}
}
}
public static class ThisQualifierVisitor extends GenericVisitor {
private final CompilationUnit fCompilationUnit;
private final List<CompilationUnitRewriteOperation> fOperations;
private final boolean fRemoveFieldQualifiers;
private final boolean fRemoveMethodQualifiers;
public ThisQualifierVisitor(boolean removeFieldQualifiers,
boolean removeMethodQualifiers,
CompilationUnit compilationUnit,
List<CompilationUnitRewriteOperation> result) {
fRemoveFieldQualifiers= removeFieldQualifiers;
fRemoveMethodQualifiers= removeMethodQualifiers;
fCompilationUnit= compilationUnit;
fOperations= result;
}
@Override
public boolean visit(final FieldAccess node) {
if (!fRemoveFieldQualifiers)
return true;
Expression expression= node.getExpression();
if (!(expression instanceof ThisExpression))
return true;
final SimpleName name= node.getName();
if (hasConflict(expression.getStartPosition(), name, ScopeAnalyzer.VARIABLES | ScopeAnalyzer.CHECK_VISIBILITY))
return true;
Name qualifier= ((ThisExpression) expression).getQualifier();
if (qualifier != null) {
ITypeBinding outerClass= (ITypeBinding) qualifier.resolveBinding();
if (outerClass == null)
return true;
IVariableBinding nameBinding= (IVariableBinding) name.resolveBinding();
if (nameBinding == null)
return true;
ITypeBinding variablesDeclaringClass= nameBinding.getDeclaringClass();
if (outerClass != variablesDeclaringClass)
//be conservative: We have a reference to a field of an outer type, and this type inherited
//the field. It's possible that the inner type inherits the same field. We must not remove
//the qualifier in this case.
return true;
ITypeBinding enclosingTypeBinding= Bindings.getBindingOfParentType(node);
if (enclosingTypeBinding == null || Bindings.isSuperType(variablesDeclaringClass, enclosingTypeBinding))
//We have a reference to a field of an outer type, and this type inherited
//the field. The inner type inherits the same field. We must not remove
//the qualifier in this case.
return true;
}
fOperations.add(new CompilationUnitRewriteOperation() {
@Override
public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModelCore model) throws CoreException {
ASTRewrite rewrite= cuRewrite.getASTRewrite();
TextEditGroup group= createTextEditGroup(FixMessages.CodeStyleFix_removeThis_groupDescription, cuRewrite);
rewrite.replace(node, rewrite.createCopyTarget(name), group);
}
});
return super.visit(node);
}
@Override
public boolean visit(final MethodInvocation node) {
if (!fRemoveMethodQualifiers)
return true;
Expression expression= node.getExpression();
if (!(expression instanceof ThisExpression))
return true;
final SimpleName name= node.getName();
if (name.resolveBinding() == null)
return true;
if (hasConflict(expression.getStartPosition(), name, ScopeAnalyzer.METHODS | ScopeAnalyzer.CHECK_VISIBILITY))
return true;
Name qualifier= ((ThisExpression)expression).getQualifier();
if (qualifier != null) {
ITypeBinding declaringClass= ((IMethodBinding)name.resolveBinding()).getDeclaringClass();
if (declaringClass == null)
return true;
ITypeBinding caller= getDeclaringType(node);
if (caller == null)
return true;
ITypeBinding callee= (ITypeBinding)qualifier.resolveBinding();
if (callee == null)
return true;
if (callee.isAssignmentCompatible(declaringClass) && caller.isAssignmentCompatible(declaringClass))
return true;
}
fOperations.add(new CompilationUnitRewriteOperation() {
@Override
public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModelCore model) throws CoreException {
ASTRewrite rewrite= cuRewrite.getASTRewrite();
TextEditGroup group= createTextEditGroup(FixMessages.CodeStyleFix_removeThis_groupDescription, cuRewrite);
rewrite.remove(node.getExpression(), group);
}
});
return super.visit(node);
}
private ITypeBinding getDeclaringType(MethodInvocation node) {
ASTNode p= node;
while (p != null) {
p= p.getParent();
if (p instanceof AbstractTypeDeclaration) {
return ((AbstractTypeDeclaration)p).resolveBinding();
}
}
return null;
}
private boolean hasConflict(int startPosition, SimpleName name, int flag) {
ScopeAnalyzer analyzer= new ScopeAnalyzer(fCompilationUnit);
for (IBinding decl : analyzer.getDeclarationsInScope(startPosition, flag)) {
if (decl.getName().equals(name.getIdentifier()) && name.resolveBinding() != decl)
return true;
}
return false;
}
}
public final static class AddThisQualifierOperation extends CompilationUnitRewriteOperation {
private final String fQualifier;
private final SimpleName fName;
public AddThisQualifierOperation(String qualifier, SimpleName name) {
fQualifier= qualifier;
fName= name;
}
public String getDescription() {
String nameLabel= BasicElementLabels.getJavaElementName(fName.getIdentifier());
String qualifierLabel;
if (fQualifier == null) {
qualifierLabel= "this"; //$NON-NLS-1$
} else {
qualifierLabel= BasicElementLabels.getJavaElementName(fQualifier + ".this"); //$NON-NLS-1$
}
return Messages.format(FixMessages.CodeStyleFix_QualifyWithThis_description, new Object[] {nameLabel, qualifierLabel});
}
@Override
public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModelCore model) throws CoreException {
ASTRewrite rewrite= cuRewrite.getASTRewrite();
TextEditGroup group= createTextEditGroup(getDescription(), cuRewrite);
AST ast= rewrite.getAST();
FieldAccess fieldAccess= ast.newFieldAccess();
ThisExpression thisExpression= ast.newThisExpression();
if (fQualifier != null)
thisExpression.setQualifier(ast.newName(fQualifier));
fieldAccess.setExpression(thisExpression);
fieldAccess.setName((SimpleName) rewrite.createMoveTarget(fName));
rewrite.replace(fName, fieldAccess, group);
}
}
public final static class AddStaticQualifierOperation extends CompilationUnitRewriteOperation {
private final SimpleName fName;
private final ITypeBinding fDeclaringClass;
public AddStaticQualifierOperation(ITypeBinding declaringClass, SimpleName name) {
super();
fDeclaringClass= declaringClass;
fName= name;
}
@Override
public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModelCore model) throws CoreException {
ASTRewrite rewrite= cuRewrite.getASTRewrite();
CompilationUnit compilationUnit= cuRewrite.getRoot();
Type type= importType(fDeclaringClass, fName, cuRewrite.getImportRewrite(), compilationUnit, TypeLocation.OTHER);
TextEditGroup group;
if (fName.resolveBinding() instanceof IMethodBinding) {
group= createTextEditGroup(FixMessages.CodeStyleFix_QualifyMethodWithDeclClass_description, cuRewrite);
} else {
group= createTextEditGroup(FixMessages.CodeStyleFix_QualifyFieldWithDeclClass_description, cuRewrite);
}
IJavaElement javaElement= fDeclaringClass.getJavaElement();
if (javaElement instanceof IType) {
IType javaElementType= (IType) javaElement;
boolean imported= !type.isNameQualifiedType() && (!type.isSimpleType() || !(((SimpleType) type).getName() instanceof QualifiedName));
Name qualifierName= compilationUnit.getAST()
.newName(imported ? javaElementType.getElementName() : javaElementType.getFullyQualifiedName());
SimpleName simpleName= (SimpleName)rewrite.createMoveTarget(fName);
QualifiedName qualifiedName= compilationUnit.getAST().newQualifiedName(qualifierName, simpleName);
rewrite.replace(fName, qualifiedName, group);
}
}
}
public final static class ToStaticAccessOperation extends CompilationUnitRewriteOperation {
private final ITypeBinding fDeclaringTypeBinding;
private final Expression fQualifier;
private final HashMap<ASTNode, Block> fCreatedBlocks;
public ToStaticAccessOperation(ITypeBinding declaringTypeBinding, Expression qualifier, HashMap<ASTNode, Block> createdBlocks) {
fDeclaringTypeBinding= declaringTypeBinding;
fQualifier= qualifier;
fCreatedBlocks= createdBlocks;
}
public String getAccessorName() {
return fDeclaringTypeBinding.getName();
}
@Override
public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModelCore model) throws CoreException {
TextEditGroup group= createTextEditGroup(FixMessages.CodeStyleFix_ChangeAccessUsingDeclaring_description, cuRewrite);
if (fQualifier instanceof MethodInvocation || fQualifier instanceof ClassInstanceCreation)
extractQualifier(fQualifier, cuRewrite, group);
Type type= importType(fDeclaringTypeBinding, fQualifier, cuRewrite.getImportRewrite(), cuRewrite.getRoot(), TypeLocation.UNKNOWN);
cuRewrite.getASTRewrite().replace(fQualifier, type, group);
}
private void extractQualifier(Expression qualifier, CompilationUnitRewrite cuRewrite, TextEditGroup group) {
Statement statement= ASTResolving.findParentStatement(qualifier);
if (statement == null)
return;
ASTRewrite astRewrite= cuRewrite.getASTRewrite();
AST ast= cuRewrite.getAST();
Expression expression= (Expression) astRewrite.createMoveTarget(qualifier);
ExpressionStatement newStatement= ast.newExpressionStatement(expression);
if (statement.getParent() instanceof Block) {
Block block= (Block) statement.getParent();
ListRewrite listRewrite= astRewrite.getListRewrite(block, Block.STATEMENTS_PROPERTY);
listRewrite.insertBefore(newStatement, statement, group);
} else {
Block block;
if (fCreatedBlocks.containsKey(statement.getParent())) {
block= fCreatedBlocks.get(statement.getParent());
} else {
block= ast.newBlock();
}
ListRewrite listRewrite= astRewrite.getListRewrite(block, Block.STATEMENTS_PROPERTY);
ASTNode lastStatement;
if (!fCreatedBlocks.containsKey(statement.getParent())) {
fCreatedBlocks.put(statement.getParent(), block);
lastStatement= astRewrite.createMoveTarget(statement);
listRewrite.insertLast(lastStatement, group);
ASTNode parent= statement.getParent();
astRewrite.set(parent, statement.getLocationInParent(), block, group);
} else {
List<?> rewrittenList= listRewrite.getRewrittenList();
lastStatement= (ASTNode) rewrittenList.get(rewrittenList.size() - 1);
}
listRewrite.insertBefore(newStatement, lastStatement, group);
}
}
}
public static CompilationUnitRewriteOperationsFixCore[] createNonStaticAccessFixes(CompilationUnit compilationUnit, IProblemLocationCore problem) {
if (!isNonStaticAccess(problem))
return null;
ToStaticAccessOperation operations[]= createToStaticAccessOperations(compilationUnit, new HashMap<ASTNode, Block>(), problem, false);
if (operations == null)
return null;
String label1= Messages.format(FixMessages.CodeStyleFix_ChangeAccessToStatic_description, operations[0].getAccessorName());
CompilationUnitRewriteOperationsFixCore fix1= new CompilationUnitRewriteOperationsFixCore(label1, compilationUnit, operations[0]);
if (operations.length > 1) {
String label2= Messages.format(FixMessages.CodeStyleFix_ChangeAccessToStaticUsingInstanceType_description, operations[1].getAccessorName());
CompilationUnitRewriteOperationsFixCore fix2= new CompilationUnitRewriteOperationsFixCore(label2, compilationUnit, operations[1]);
return new CompilationUnitRewriteOperationsFixCore[] {fix1, fix2};
}
return new CompilationUnitRewriteOperationsFixCore[] {fix1};
}
public static CompilationUnitRewriteOperationsFixCore createAddFieldQualifierFix(CompilationUnit compilationUnit, IProblemLocationCore problem) {
if (IProblem.UnqualifiedFieldAccess != problem.getProblemId())
return null;
AddThisQualifierOperation operation= getUnqualifiedFieldAccessResolveOperation(compilationUnit, problem);
if (operation == null)
return null;
String groupName= operation.getDescription();
return new CodeStyleFixCore(groupName, compilationUnit, new CompilationUnitRewriteOperation[] {operation});
}
public static CompilationUnitRewriteOperationsFixCore createIndirectAccessToStaticFix(CompilationUnit compilationUnit, IProblemLocationCore problem) {
if (!isIndirectStaticAccess(problem))
return null;
ToStaticAccessOperation operations[]= createToStaticAccessOperations(compilationUnit, new HashMap<ASTNode, Block>(), problem, false);
if (operations == null)
return null;
String label= Messages.format(FixMessages.CodeStyleFix_ChangeStaticAccess_description, operations[0].getAccessorName());
return new CodeStyleFixCore(label, compilationUnit, new CompilationUnitRewriteOperation[] {operations[0]});
}
public static ICleanUpFixCore createCleanUp(CompilationUnit compilationUnit,
boolean addThisQualifier,
boolean changeNonStaticAccessToStatic,
boolean qualifyStaticFieldAccess,
boolean changeIndirectStaticAccessToDirect,
boolean qualifyMethodAccess,
boolean qualifyStaticMethodAccess,
boolean removeFieldQualifier,
boolean removeMethodQualifier) {
if (!addThisQualifier && !changeNonStaticAccessToStatic && !qualifyStaticFieldAccess && !changeIndirectStaticAccessToDirect && !qualifyMethodAccess && !qualifyStaticMethodAccess && !removeFieldQualifier && !removeMethodQualifier)
return null;
List<CompilationUnitRewriteOperation> operations= new ArrayList<>();
if (addThisQualifier || qualifyStaticFieldAccess || qualifyMethodAccess || qualifyStaticMethodAccess) {
CodeStyleVisitor codeStyleVisitor= new CodeStyleVisitor(compilationUnit, addThisQualifier, qualifyStaticFieldAccess, qualifyMethodAccess, qualifyStaticMethodAccess, operations);
compilationUnit.accept(codeStyleVisitor);
}
IProblem[] problems= compilationUnit.getProblems();
IProblemLocationCore[] locations= new IProblemLocationCore[problems.length];
for (int i= 0; i < problems.length; i++) {
locations[i]= new ProblemLocationCore(problems[i]);
}
addToStaticAccessOperations(compilationUnit, locations, changeNonStaticAccessToStatic, changeIndirectStaticAccessToDirect, operations);
if (removeFieldQualifier || removeMethodQualifier) {
ThisQualifierVisitor visitor= new ThisQualifierVisitor(removeFieldQualifier, removeMethodQualifier, compilationUnit, operations);
compilationUnit.accept(visitor);
}
if (operations.isEmpty())
return null;
CompilationUnitRewriteOperation[] operationsArray= operations.toArray(new CompilationUnitRewriteOperation[operations.size()]);
return new CodeStyleFixCore(FixMessages.CodeStyleFix_change_name, compilationUnit, operationsArray);
}
public static ICleanUpFixCore createCleanUp(CompilationUnit compilationUnit, IProblemLocationCore[] problems,
boolean addThisQualifier,
boolean changeNonStaticAccessToStatic,
boolean changeIndirectStaticAccessToDirect) {
if (!addThisQualifier && !changeNonStaticAccessToStatic && !changeIndirectStaticAccessToDirect)
return null;
List<CompilationUnitRewriteOperation> operations= new ArrayList<>();
if (addThisQualifier) {
for (IProblemLocationCore problem : problems) {
if (problem.getProblemId() == IProblem.UnqualifiedFieldAccess) {
AddThisQualifierOperation operation= getUnqualifiedFieldAccessResolveOperation(compilationUnit, problem);
if (operation != null)
operations.add(operation);
}
}
}
addToStaticAccessOperations(compilationUnit, problems, changeNonStaticAccessToStatic, changeIndirectStaticAccessToDirect, operations);
if (operations.isEmpty())
return null;
CompilationUnitRewriteOperation[] operationsArray= operations.toArray(new CompilationUnitRewriteOperation[operations.size()]);
return new CodeStyleFixCore(FixMessages.CodeStyleFix_change_name, compilationUnit, operationsArray);
}
public static void addToStaticAccessOperations(CompilationUnit compilationUnit, IProblemLocationCore[] problems, boolean changeNonStaticAccessToStatic, boolean changeIndirectStaticAccessToDirect, List<CompilationUnitRewriteOperation> result) {
if (!changeNonStaticAccessToStatic && !changeIndirectStaticAccessToDirect)
return;
List<ToStaticAccessOperation> operations= new ArrayList<>();
HashMap<ASTNode, Block> createdBlocks= new HashMap<>();
for (IProblemLocationCore problem : problems) {
boolean isNonStaticAccess= changeNonStaticAccessToStatic && isNonStaticAccess(problem);
boolean isIndirectStaticAccess= changeIndirectStaticAccessToDirect && isIndirectStaticAccess(problem);
if (isNonStaticAccess || isIndirectStaticAccess) {
ToStaticAccessOperation[] nonStaticAccessInformation= createToStaticAccessOperations(compilationUnit, createdBlocks, problem, true);
if (nonStaticAccessInformation != null) {
ToStaticAccessOperation op= nonStaticAccessInformation[0];
Expression qualifier= op.fQualifier;
for (CompilationUnitRewriteOperation oper : result) { // see bug 346230
if (oper instanceof CodeStyleFixCore.AddThisQualifierOperation
&& ((CodeStyleFixCore.AddThisQualifierOperation) oper).fName.equals(qualifier)) {
result.remove(oper);
break;
}
}
operations.add(op);
}
}
}
// Make sure qualifiers are processed inside-out and left-to-right, so that
// ToStaticAccessOperation#extractQualifier(..) extracts qualifiers in execution order:
Collections.sort(operations, (o1, o2) -> {
if (ASTNodes.isParent(o1.fQualifier, o2.fQualifier)) {
return -1;
} else if (ASTNodes.isParent(o2.fQualifier, o1.fQualifier)) {
return 1;
} else {
return o1.fQualifier.getStartPosition() - o2.fQualifier.getStartPosition();
}
});
result.addAll(operations);
}
public static boolean isIndirectStaticAccess(IProblemLocationCore problem) {
return (problem.getProblemId() == IProblem.IndirectAccessToStaticField
|| problem.getProblemId() == IProblem.IndirectAccessToStaticMethod);
}
public static boolean isNonStaticAccess(IProblemLocationCore problem) {
return (problem.getProblemId() == IProblem.NonStaticAccessToStaticField
|| problem.getProblemId() == IProblem.NonStaticAccessToStaticMethod
|| problem.getProblemId() == IProblem.NonStaticOrAlienTypeReceiver);
}
public static ToStaticAccessOperation[] createToStaticAccessOperations(CompilationUnit astRoot, HashMap<ASTNode, Block> createdBlocks, IProblemLocationCore problem, boolean conservative) {
ASTNode selectedNode= problem.getCoveringNode(astRoot);
if (selectedNode == null) {
return null;
}
Expression qualifier= null;
IBinding accessBinding= null;
if (selectedNode instanceof SimpleName) {
selectedNode= selectedNode.getParent();
}
if (selectedNode instanceof QualifiedName) {
QualifiedName name= (QualifiedName) selectedNode;
qualifier= name.getQualifier();
accessBinding= name.resolveBinding();
} else if (selectedNode instanceof MethodInvocation) {
MethodInvocation methodInvocation= (MethodInvocation) selectedNode;
qualifier= methodInvocation.getExpression();
accessBinding= methodInvocation.getName().resolveBinding();
} else if (selectedNode instanceof FieldAccess) {
FieldAccess fieldAccess= (FieldAccess) selectedNode;
qualifier= fieldAccess.getExpression();
accessBinding= fieldAccess.getName().resolveBinding();
}
if (accessBinding != null && qualifier != null) {
if (conservative && ASTResolving.findParentStatement(qualifier) == null)
return null;
ToStaticAccessOperation declaring= null;
ITypeBinding declaringTypeBinding= getDeclaringTypeBinding(accessBinding);
if (declaringTypeBinding != null) {
declaringTypeBinding= declaringTypeBinding.getTypeDeclaration(); // use generic to avoid any type arguments
int modifiers= declaringTypeBinding.getModifiers();
if (!Modifier.isPublic(modifiers) && !Modifier.isProtected(modifiers) && !Modifier.isPrivate(modifiers)) {
PackageDeclaration packageDecl= astRoot.getPackage();
if (packageDecl == null) {
if (declaringTypeBinding.getPackage() != null) {
return null;
}
} else {
if (!declaringTypeBinding.getPackage().isEqualTo(packageDecl.resolveBinding())) {
return null;
}
}
}
declaring= new ToStaticAccessOperation(declaringTypeBinding, qualifier, createdBlocks);
}
ToStaticAccessOperation instance= null;
ITypeBinding instanceTypeBinding= Bindings.normalizeTypeBinding(qualifier.resolveTypeBinding());
if (instanceTypeBinding != null) {
instanceTypeBinding= instanceTypeBinding.getTypeDeclaration(); // use generic to avoid any type arguments
if (instanceTypeBinding.getTypeDeclaration() != declaringTypeBinding) {
instance= new ToStaticAccessOperation(instanceTypeBinding, qualifier, createdBlocks);
}
}
if (declaring != null && instance != null) {
return new ToStaticAccessOperation[] {declaring, instance};
} else {
return new ToStaticAccessOperation[] {declaring};
}
}
return null;
}
private static ITypeBinding getDeclaringTypeBinding(IBinding accessBinding) {
if (accessBinding instanceof IMethodBinding) {
return ((IMethodBinding) accessBinding).getDeclaringClass();
} else if (accessBinding instanceof IVariableBinding) {
return ((IVariableBinding) accessBinding).getDeclaringClass();
}
return null;
}
public static AddThisQualifierOperation getUnqualifiedFieldAccessResolveOperation(CompilationUnit compilationUnit, IProblemLocationCore problem) {
SimpleName name= getName(compilationUnit, problem);
if (name == null)
return null;
IBinding binding= name.resolveBinding();
if (binding == null || binding.getKind() != IBinding.VARIABLE)
return null;
ImportRewrite imports= StubUtility.createImportRewrite(compilationUnit, true);
String replacement= getThisExpressionQualifier(((IVariableBinding) binding).getDeclaringClass(), imports, name);
if (replacement == null)
return null;
if (replacement.length() == 0)
replacement= null;
return new AddThisQualifierOperation(replacement, name);
}
private static String getThisExpressionQualifier(ITypeBinding declaringClass, ImportRewrite imports, SimpleName name) {
ITypeBinding parentType= Bindings.getBindingOfParentType(name);
ITypeBinding currType= parentType;
while (currType != null && !Bindings.isSuperType(declaringClass, currType)) {
currType= currType.getDeclaringClass();
}
if (currType == null) {
declaringClass= declaringClass.getTypeDeclaration();
currType= parentType;
while (currType != null && !Bindings.isSuperType(declaringClass, currType)) {
currType= currType.getDeclaringClass();
}
}
if (currType != parentType) {
if (currType == null)
return null;
if (currType.isAnonymous())
//If we access a field of a super class of an anonymous class
//then we can only qualify with 'this' but not with outer.this
//see bug 115277
return null;
return imports.addImport(currType);
} else {
return ""; //$NON-NLS-1$
}
}
private static SimpleName getName(CompilationUnit compilationUnit, IProblemLocationCore problem) {
ASTNode selectedNode= problem.getCoveringNode(compilationUnit);
while (selectedNode instanceof QualifiedName) {
selectedNode= ((QualifiedName) selectedNode).getQualifier();
}
if (!(selectedNode instanceof SimpleName)) {
return null;
}
return (SimpleName) selectedNode;
}
private CodeStyleFixCore(String name, CompilationUnit compilationUnit, CompilationUnitRewriteOperation[] operations) {
super(name, compilationUnit, operations);
}
}