blob: b8b68d54a98c71a56378856de7b19d19baa92a5d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2019, 2021 Red Hat Inc. 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:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.corext.fix;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ArrayCreation;
import org.eclipse.jdt.core.dom.ArrayInitializer;
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.IMethodBinding;
import org.eclipse.jdt.core.dom.IPackageBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.NullLiteral;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.SuperMethodInvocation;
import org.eclipse.jdt.core.dom.ThisExpression;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.dom.GenericVisitor;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
import org.eclipse.jdt.ui.cleanup.ICleanUpFix;
public class UnnecessaryArrayCreationFix extends CompilationUnitRewriteOperationsFix {
public final static class UnnecessaryArrayCreationFinder extends GenericVisitor {
private final List<CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation> fResult;
private final boolean fRemoveUnnecessaryArrayCreation;
private final Set<String> fInvalidTypes= new HashSet<>(Arrays.asList("byte", "char", "short")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
public UnnecessaryArrayCreationFinder(boolean removeUnnecessaryArrayCreation, List<CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation> resultingCollection) {
fRemoveUnnecessaryArrayCreation= removeUnnecessaryArrayCreation;
fResult= resultingCollection;
}
@Override
public boolean visit(ArrayCreation node) {
if (!fRemoveUnnecessaryArrayCreation
|| node.getType().getDimensions() != 1) {
return true;
}
ArrayInitializer initializer= node.getInitializer();
if (initializer != null
&& initializer.expressions() != null
&& initializer.expressions().size() == 1) {
List<Expression> expressions= initializer.expressions();
ITypeBinding singleElement= expressions.get(0).resolveTypeBinding();
NullLiteral nullLiteral= ASTNodes.as(expressions.get(0), NullLiteral.class);
if (nullLiteral != null
|| singleElement == null
|| singleElement.isArray()) {
return true;
}
}
ASTNode parent= node.getParent();
if (parent instanceof ClassInstanceCreation) {
ClassInstanceCreation cic= (ClassInstanceCreation) parent;
if (canArrayBeRemoved(node, cic.arguments(), cic.resolveConstructorBinding())) {
fResult.add(new UnwrapNewArrayOperation(node, cic));
}
} else if (parent instanceof MethodInvocation) {
MethodInvocation m= (MethodInvocation) parent;
if (canArrayBeRemoved(node, m.arguments(), m.resolveMethodBinding())) {
fResult.add(new UnwrapNewArrayOperation(node, m));
}
} else if (parent instanceof SuperMethodInvocation) {
SuperMethodInvocation sm= (SuperMethodInvocation) parent;
if (canArrayBeRemoved(node, sm.arguments(), sm.resolveMethodBinding())) {
fResult.add(new UnwrapNewArrayOperation(node, sm));
}
}
return true;
}
private boolean canArrayBeRemoved(ArrayCreation node, List<Expression> arguments, IMethodBinding binding) {
return isUselessArrayCreation(node, arguments, binding)
&& !hasEquivalentMethod(node, arguments, binding);
}
private boolean isUselessArrayCreation(ArrayCreation node, List<Expression> arguments, IMethodBinding binding) {
return (node.getInitializer() != null || (node.dimensions().size() == 1 && Long.valueOf(0L).equals(ASTNodes.getIntegerLiteral((Expression) node.dimensions().get(0)))))
&& !arguments.isEmpty()
&& arguments.get(arguments.size() - 1) == node
&& binding != null
&& binding.isVarargs()
&& binding.getParameterTypes().length == arguments.size()
&& binding.getParameterTypes()[arguments.size() - 1].getDimensions() == 1
&& !fInvalidTypes.contains(binding.getParameterTypes()[arguments.size() - 1].getElementType().getName());
}
private boolean hasEquivalentMethod(ArrayCreation node, List<Expression> arguments, IMethodBinding binding) {
TypeDeclaration typeDeclaration= ASTNodes.getTypedAncestor(node, TypeDeclaration.class);
if (typeDeclaration == null) {
return true;
}
ITypeBinding type= typeDeclaration.resolveBinding();
if (type == null) {
return true;
}
boolean inSameClass= true;
ASTNode parent= node.getParent();
ITypeBinding[] parameterTypesForConflictingMethod= getParameterTypesForConflictingMethod(arguments, node);
// Figure out the type where we need to start looking at methods in the hierarchy.
// If we have a new class instance or super method call or this expression or
// we have a static call that is qualified, we use the referenced class as the starting point.
// If we have a non-qualified method call, we use the class containing the call.
// Otherwise, we bail on the clean-up.
if (parent instanceof ClassInstanceCreation) {
type= ((ClassInstanceCreation) parent).resolveTypeBinding();
inSameClass= type.isNested();
} else if (parent instanceof MethodInvocation) {
MethodInvocation methodInvocation= (MethodInvocation) parent;
Expression expression= methodInvocation.getExpression();
if (expression != null) {
if (!(expression instanceof ThisExpression)) {
inSameClass= binding.getDeclaringClass().isEqualTo(type);
type= expression.resolveTypeBinding();
}
} else {
ASTNode root= node.getRoot();
if (root instanceof CompilationUnit) {
CompilationUnit compilationUnit= (CompilationUnit) root;
List<ImportDeclaration> imports= compilationUnit.imports();
String localPackage= null;
if (compilationUnit.getPackage() != null && compilationUnit.getPackage().getName() != null) {
localPackage= compilationUnit.getPackage().getName().getFullyQualifiedName();
}
for (ImportDeclaration oneImport : imports) {
if (oneImport.isStatic()
&& !oneImport.isOnDemand()
&& oneImport.getName() instanceof QualifiedName) {
QualifiedName methodName= (QualifiedName) oneImport.getName();
String methodIdentifier= methodName.getName().getIdentifier();
ITypeBinding conflictingType= methodName.getQualifier().resolveTypeBinding();
if (conflictingType == null) {
return true; // Error on side of caution
}
String importPackage= null;
if (conflictingType.getPackage() != null) {
importPackage= conflictingType.getPackage().getName();
}
boolean inSamePackage= Objects.equals(localPackage, importPackage);
for (IMethodBinding declaredMethod : conflictingType.getDeclaredMethods()) {
if (methodIdentifier.equals(declaredMethod.getName())
&& isMethodMatching(parameterTypesForConflictingMethod, binding, false, inSamePackage, declaredMethod)) {
return true;
}
}
}
}
}
if (Modifier.isStatic(binding.getModifiers())) {
inSameClass= binding.getDeclaringClass().isEqualTo(type);
type= binding.getDeclaringClass();
}
}
} else if (parent instanceof SuperMethodInvocation) {
inSameClass= type.isNested();
type= type.getSuperclass();
} else {
return true; // Error on side of caution
}
if (type == null) {
return true;
}
return hasEquivalentMethodForInheritedTypes(parameterTypesForConflictingMethod, binding, type, type, inSameClass);
}
private ITypeBinding[] getParameterTypesForConflictingMethod(List<Expression> arguments, ArrayCreation node) {
ArrayInitializer initializer= node.getInitializer();
List<Expression> initializerExpressions;
if (initializer != null) {
initializerExpressions= initializer.expressions();
} else {
initializerExpressions= Collections.EMPTY_LIST;
}
ITypeBinding[] parameterTypesForConflictingMethod= new ITypeBinding[arguments.size() - 1 + initializerExpressions.size()];
for (int i= 0; i < arguments.size() - 1; i++) {
parameterTypesForConflictingMethod[i]= arguments.get(i).resolveTypeBinding();
}
for (int i= 0; i < initializerExpressions.size(); i++) {
parameterTypesForConflictingMethod[arguments.size() - 1 + i]= initializerExpressions.get(i).resolveTypeBinding();
}
return parameterTypesForConflictingMethod;
}
private boolean hasEquivalentMethodForInheritedTypes(ITypeBinding[] parameterTypesForConflictingMethod, IMethodBinding binding, ITypeBinding type,
ITypeBinding origType, boolean inSameClass) {
while (type != null) {
IPackageBinding packageBinding= type.getPackage();
boolean inSamePackage= packageBinding.isEqualTo(origType.getPackage());
if (hasEquivalentMethodForOneType(parameterTypesForConflictingMethod, binding, type, inSameClass, inSamePackage)) {
return true;
}
if (type.isNested()) {
if (hasEquivalentMethodForInheritedTypes(parameterTypesForConflictingMethod, binding, type.getDeclaringClass(), origType, inSameClass)) {
return true;
}
type= type.getSuperclass();
inSameClass&= type.isNested();
} else {
type= type.getSuperclass();
inSameClass= false;
}
}
return false;
}
private boolean hasEquivalentMethodForOneType(ITypeBinding[] parameterTypesForConflictingMethod, IMethodBinding binding,
ITypeBinding type, boolean inSameClass, boolean inSamePackage) {
for (IMethodBinding method : type.getDeclaredMethods()) {
if (isMethodMatching(parameterTypesForConflictingMethod, binding, inSameClass, inSamePackage, method)) {
return true;
}
}
return false;
}
private boolean isMethodMatching(ITypeBinding[] parameterTypesForConflictingMethod, IMethodBinding binding, boolean inSameClass, boolean inSamePackage, IMethodBinding testedMethod) {
int methodModifiers= testedMethod.getModifiers();
ITypeBinding[] parameterTypes= testedMethod.getParameterTypes();
if (!binding.isEqualTo(testedMethod)
&& parameterTypesForConflictingMethod.length == parameterTypes.length
&& binding.getName().equals(testedMethod.getName())
&& (inSameClass || Modifier.isPublic(methodModifiers) || Modifier.isProtected(methodModifiers)
|| (inSamePackage && !Modifier.isPrivate(methodModifiers)))) {
for (int i= 0; i < parameterTypesForConflictingMethod.length; i++) {
if (parameterTypesForConflictingMethod[i] == null || parameterTypes[i] == null) {
return true;
}
if (!parameterTypesForConflictingMethod[i].isAssignmentCompatible(parameterTypes[i])) {
return false;
}
}
return true;
}
return false;
}
}
public static ICleanUpFix createCleanUp(CompilationUnit compilationUnit, boolean removeUnnecessaryArrayCreation) {
if (!JavaModelUtil.is50OrHigher(compilationUnit.getJavaElement().getJavaProject()))
return null;
if (!removeUnnecessaryArrayCreation)
return null;
List<CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation> operations= new ArrayList<>();
UnnecessaryArrayCreationFix.UnnecessaryArrayCreationFinder finder= new UnnecessaryArrayCreationFix.UnnecessaryArrayCreationFinder(removeUnnecessaryArrayCreation, operations);
compilationUnit.accept(finder);
if (operations.isEmpty())
return null;
CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation[] ops= operations.toArray(new CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation[operations.size()]);
return new ConvertLoopFix(FixMessages.ControlStatementsFix_change_name, compilationUnit, ops, null);
}
public static UnnecessaryArrayCreationFix createUnnecessaryArrayCreationFix(CompilationUnit compilationUnit, Expression methodInvocation) {
if (!JavaModelUtil.is50OrHigher(compilationUnit.getJavaElement().getJavaProject()))
return null;
List<CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation> operations= new ArrayList<>();
UnnecessaryArrayCreationFix.UnnecessaryArrayCreationFinder finder= new UnnecessaryArrayCreationFix.UnnecessaryArrayCreationFinder(true, operations);
methodInvocation.accept(finder);
if (operations.isEmpty())
return null;
return new UnnecessaryArrayCreationFix(FixMessages.Java50Fix_RemoveUnnecessaryArrayCreation_description, compilationUnit, new CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation[] {operations.get(0)}, null);
}
private final IStatus fStatus;
protected UnnecessaryArrayCreationFix(String name, CompilationUnit compilationUnit, CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation[] fixRewriteOperations, IStatus status) {
super(name, compilationUnit, fixRewriteOperations);
fStatus= status;
}
@Override
public IStatus getStatus() {
return fStatus;
}
}