| /******************************************************************************* |
| * Copyright (c) 2020 Fabrice TIERCELIN 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: |
| * Fabrice TIERCELIN - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.ui.fix; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.concurrent.atomic.AtomicInteger; |
| import java.util.concurrent.atomic.AtomicLong; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| import org.eclipse.core.runtime.CoreException; |
| |
| import org.eclipse.text.edits.TextEditGroup; |
| |
| import org.eclipse.jdt.core.ICompilationUnit; |
| 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.AnonymousClassDeclaration; |
| import org.eclipse.jdt.core.dom.ArrayAccess; |
| import org.eclipse.jdt.core.dom.ArrayCreation; |
| 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.ITypeBinding; |
| import org.eclipse.jdt.core.dom.IVariableBinding; |
| import org.eclipse.jdt.core.dom.LambdaExpression; |
| import org.eclipse.jdt.core.dom.MethodInvocation; |
| import org.eclipse.jdt.core.dom.NullLiteral; |
| import org.eclipse.jdt.core.dom.ParameterizedType; |
| import org.eclipse.jdt.core.dom.PostfixExpression; |
| import org.eclipse.jdt.core.dom.PrefixExpression; |
| import org.eclipse.jdt.core.dom.SimpleName; |
| import org.eclipse.jdt.core.dom.SingleVariableDeclaration; |
| import org.eclipse.jdt.core.dom.Type; |
| import org.eclipse.jdt.core.dom.VariableDeclarationExpression; |
| 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.internal.corext.dom.ASTNodeFactory; |
| import org.eclipse.jdt.internal.corext.dom.ASTNodes; |
| import org.eclipse.jdt.internal.corext.fix.CleanUpConstants; |
| import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix; |
| import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix.CompilationUnitRewriteOperation; |
| import org.eclipse.jdt.internal.corext.fix.LinkedProposalModel; |
| import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite; |
| import org.eclipse.jdt.internal.corext.util.JavaModelUtil; |
| |
| import org.eclipse.jdt.ui.cleanup.CleanUpRequirements; |
| import org.eclipse.jdt.ui.cleanup.ICleanUpFix; |
| import org.eclipse.jdt.ui.text.java.IProblemLocation; |
| |
| /** |
| * A fix that replaces an array with one index by an atomic object. |
| */ |
| public class AtomicObjectCleanUp extends AbstractMultiFix { |
| public AtomicObjectCleanUp() { |
| this(Collections.emptyMap()); |
| } |
| |
| public AtomicObjectCleanUp(Map<String, String> options) { |
| super(options); |
| } |
| |
| @Override |
| public CleanUpRequirements getRequirements() { |
| boolean requireAST= isEnabled(CleanUpConstants.ATOMIC_OBJECT); |
| return new CleanUpRequirements(requireAST, false, false, null); |
| } |
| |
| @Override |
| public String[] getStepDescriptions() { |
| if (isEnabled(CleanUpConstants.ATOMIC_OBJECT)) { |
| return new String[] { MultiFixMessages.CodeStyleCleanUp_AtomicObject_description }; |
| } |
| |
| return new String[0]; |
| } |
| |
| @Override |
| public String getPreview() { |
| if (isEnabled(CleanUpConstants.ATOMIC_OBJECT)) { |
| return "AtomicBoolean booleanRef= new AtomicBoolean();\n" //$NON-NLS-1$ |
| + "Runnable runnable = () -> booleanRef.set(true);\n" //$NON-NLS-1$ |
| + "runnable.run();\n" //$NON-NLS-1$ |
| + "boolean b = booleanRef.get();\n"; //$NON-NLS-1$ |
| } |
| |
| return "boolean[] booleanRef= new boolean[1];\n" //$NON-NLS-1$ |
| + "Runnable runnable = () -> booleanRef[0] = true;\n" //$NON-NLS-1$ |
| + "runnable.run();\n" //$NON-NLS-1$ |
| + "boolean b = booleanRef[0];\n"; //$NON-NLS-1$ |
| } |
| |
| @Override |
| protected ICleanUpFix createFix(CompilationUnit unit) throws CoreException { |
| if (!isEnabled(CleanUpConstants.ATOMIC_OBJECT) || !JavaModelUtil.is50OrHigher(unit.getJavaElement().getJavaProject())) { |
| return null; |
| } |
| |
| final List<CompilationUnitRewriteOperation> rewriteOperations= new ArrayList<>(); |
| |
| unit.accept(new ASTVisitor() { |
| @Override |
| public boolean visit(final Block node) { |
| ArrayOccurrencesVisitor arrayOccurrencesVisitor= new ArrayOccurrencesVisitor(node); |
| node.accept(arrayOccurrencesVisitor); |
| return arrayOccurrencesVisitor.result; |
| } |
| |
| final class ArrayOccurrencesVisitor extends ASTVisitor { |
| private final Block startNode; |
| private boolean result= true; |
| |
| public ArrayOccurrencesVisitor(final Block startNode) { |
| this.startNode= startNode; |
| } |
| |
| @Override |
| public boolean visit(final Block node) { |
| return startNode == node; |
| } |
| |
| @Override |
| public boolean visit(final VariableDeclarationStatement node) { |
| if (node.fragments().size() != 1) { |
| return true; |
| } |
| |
| VariableDeclarationFragment fragment= (VariableDeclarationFragment) node.fragments().get(0); |
| return visitVariable(node.getType(), fragment.resolveBinding(), fragment.extraDimensions(), fragment.getName(), fragment.getInitializer()); |
| } |
| |
| @Override |
| public boolean visit(final VariableDeclarationExpression node) { |
| if (node.fragments().size() != 1) { |
| return true; |
| } |
| |
| VariableDeclarationFragment fragment= (VariableDeclarationFragment) node.fragments().get(0); |
| return visitVariable(node.getType(), fragment.resolveBinding(), fragment.extraDimensions(), fragment.getName(), fragment.getInitializer()); |
| } |
| |
| @Override |
| public boolean visit(final SingleVariableDeclaration node) { |
| return visitVariable(node.getType(), node.resolveBinding(), node.extraDimensions(), node.getName(), node.getInitializer()); |
| } |
| |
| private boolean visitVariable(final Type type, final IVariableBinding variableBinding, final List<?> variableDimensions, final SimpleName declaration, final Expression initializer) { |
| ArrayCreation arrayCreation= ASTNodes.as(initializer, ArrayCreation.class); |
| |
| if (result |
| && arrayCreation != null |
| && (arrayCreation.getInitializer() != null |
| ? arrayCreation.getInitializer().expressions().size() == 1 |
| : arrayCreation.dimensions().size() == 1 && Long.valueOf(1L).equals(ASTNodes.getIntegerLiteral((Expression) arrayCreation.dimensions().get(0)))) |
| && type != null |
| && type.resolveBinding() != null |
| && (type.resolveBinding().isArray() |
| ? variableDimensions.isEmpty() && type.resolveBinding().getDimensions() == 1 && equalNotNull(type.resolveBinding().getElementType(), arrayCreation.getType().getElementType().resolveBinding()) |
| : variableDimensions.size() == 1 && equalNotNull(type.resolveBinding(), arrayCreation.getType().getElementType().resolveBinding())) |
| && !ASTNodes.hasType(arrayCreation.getType().getElementType().resolveBinding(), |
| double.class.getCanonicalName(), |
| float.class.getCanonicalName(), |
| short.class.getCanonicalName(), |
| char.class.getCanonicalName(), |
| byte.class.getCanonicalName())) { |
| VarDefinitionsUsesVisitor varOccurrencesVisitor= new VarDefinitionsUsesVisitor(variableBinding, |
| startNode, true); |
| |
| List<SimpleName> reads= varOccurrencesVisitor.getReads(); |
| List<SimpleName> writes= varOccurrencesVisitor.getWrites(); |
| writes.remove(declaration); |
| |
| if (writes.isEmpty()) { |
| Set<Assignment> assignmentReads= new HashSet<>(); |
| Set<ArrayAccess> accessReads= new HashSet<>(); |
| |
| for (SimpleName simpleName : reads) { |
| if (!isReadValid(simpleName, assignmentReads, accessReads)) { |
| return true; |
| } |
| } |
| |
| boolean hasOneWriteInDynamicCode= false; |
| |
| for (Assignment assignmentRead : assignmentReads) { |
| ASTNode dynamicCode= ASTNodes.getFirstAncestorOrNull(assignmentRead, LambdaExpression.class, AnonymousClassDeclaration.class); |
| |
| if (dynamicCode != null && ASTNodes.isParent(dynamicCode, startNode)) { |
| hasOneWriteInDynamicCode= true; |
| break; |
| } |
| } |
| |
| if (hasOneWriteInDynamicCode) { |
| rewriteOperations.add(new AtomicObjectOperation(type, variableDimensions, arrayCreation, assignmentReads, accessReads)); |
| |
| result= false; |
| return false; |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| private boolean equalNotNull(ITypeBinding elementType, ITypeBinding resolveBinding) { |
| return elementType != null && Objects.equals(elementType, resolveBinding); |
| } |
| |
| private boolean isReadValid(final SimpleName simpleName, final Set<Assignment> assignmentReads, final Set<ArrayAccess> accessReads) { |
| if (simpleName.getParent() instanceof ArrayAccess |
| && simpleName.getLocationInParent() == ArrayAccess.ARRAY_PROPERTY) { |
| ArrayAccess arrayAccess= (ArrayAccess) simpleName.getParent(); |
| |
| if (Long.valueOf(0L).equals(ASTNodes.getIntegerLiteral(arrayAccess.getIndex()))) { |
| if (arrayAccess.getParent() instanceof Assignment |
| && arrayAccess.getLocationInParent() == Assignment.LEFT_HAND_SIDE_PROPERTY) { |
| Assignment assignment= (Assignment) arrayAccess.getParent(); |
| |
| if (ASTNodes.hasOperator(assignment, Assignment.Operator.ASSIGN) |
| && (assignment.getParent() instanceof ExpressionStatement |
| || assignment.getParent() instanceof LambdaExpression && assignment.getLocationInParent() == LambdaExpression.BODY_PROPERTY)) { |
| assignmentReads.add(assignment); |
| return true; |
| } |
| } else if ((!(arrayAccess.getParent() instanceof PrefixExpression) |
| || !ASTNodes.hasOperator((PrefixExpression) arrayAccess.getParent(), PrefixExpression.Operator.INCREMENT, PrefixExpression.Operator.DECREMENT)) |
| && (!(arrayAccess.getParent() instanceof PostfixExpression) |
| || !ASTNodes.hasOperator((PostfixExpression) arrayAccess.getParent(), PostfixExpression.Operator.INCREMENT, PostfixExpression.Operator.DECREMENT))) { |
| accessReads.add(arrayAccess); |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| } |
| }); |
| |
| if (rewriteOperations.isEmpty()) { |
| return null; |
| } |
| |
| return new CompilationUnitRewriteOperationsFix(MultiFixMessages.CodeStyleCleanUp_AtomicObject_description, unit, |
| rewriteOperations.toArray(new CompilationUnitRewriteOperation[0])); |
| } |
| |
| @Override |
| public boolean canFix(final ICompilationUnit compilationUnit, final IProblemLocation problem) { |
| return false; |
| } |
| |
| @Override |
| protected ICleanUpFix createFix(final CompilationUnit unit, final IProblemLocation[] problems) throws CoreException { |
| return null; |
| } |
| |
| private static class AtomicObjectOperation extends CompilationUnitRewriteOperation { |
| private final Type type; |
| private final List<?> variableDimensions; |
| private final ArrayCreation arrayCreation; |
| private final Set<Assignment> assignmentReads; |
| private final Set<ArrayAccess> accessReads; |
| |
| public AtomicObjectOperation(final Type type, final List<?> variableDimensions, final ArrayCreation arrayCreation, final Set<Assignment> assignmentReads, final Set<ArrayAccess> accessReads) { |
| this.type= type; |
| this.variableDimensions= variableDimensions; |
| this.arrayCreation= arrayCreation; |
| this.assignmentReads= assignmentReads; |
| this.accessReads= accessReads; |
| } |
| |
| @Override |
| public void rewriteAST(final CompilationUnitRewrite cuRewrite, final LinkedProposalModel linkedModel) throws CoreException { |
| ASTRewrite rewrite= cuRewrite.getASTRewrite(); |
| AST ast= cuRewrite.getRoot().getAST(); |
| ImportRewrite importRewrite= cuRewrite.getImportRewrite(); |
| TextEditGroup groupdeclaration= createTextEditGroup(MultiFixMessages.CodeStyleCleanUp_AtomicObject_declaration, cuRewrite); |
| |
| Class<?> atomicClass; |
| Type objectClass= null; |
| if (ASTNodes.hasType(arrayCreation.getType().getElementType().resolveBinding(), boolean.class.getCanonicalName())) { |
| atomicClass= AtomicBoolean.class; |
| } else if (ASTNodes.hasType(arrayCreation.getType().getElementType().resolveBinding(), int.class.getCanonicalName())) { |
| atomicClass= AtomicInteger.class; |
| } else if (ASTNodes.hasType(arrayCreation.getType().getElementType().resolveBinding(), long.class.getCanonicalName())) { |
| atomicClass= AtomicLong.class; |
| } else { |
| atomicClass= AtomicReference.class; |
| objectClass= arrayCreation.getType().getElementType(); |
| } |
| |
| String atomicClassNameText= importRewrite.addImport(atomicClass.getCanonicalName()); |
| Type atomicInstance= ast.newSimpleType(ASTNodeFactory.newName(ast, atomicClassNameText)); |
| |
| if (objectClass != null) { |
| ParameterizedType newParameterizedType= ast.newParameterizedType(atomicInstance); |
| |
| if (!JavaModelUtil.is1d7OrHigher(((CompilationUnit) type.getRoot()).getJavaElement().getJavaProject())) { |
| newParameterizedType.typeArguments().add(rewrite.createCopyTarget(objectClass)); |
| } |
| |
| atomicInstance= newParameterizedType; |
| } |
| |
| ClassInstanceCreation newAtomicObject= ast.newClassInstanceCreation(); |
| newAtomicObject.setType(atomicInstance); |
| |
| if (arrayCreation.getInitializer() != null |
| && !ASTNodes.is((Expression) arrayCreation.getInitializer().expressions().get(0), NullLiteral.class)) { |
| List<Expression> arguments= newAtomicObject.arguments(); |
| arguments.add(ASTNodes.createMoveTarget(rewrite, (Expression) arrayCreation.getInitializer().expressions().get(0))); |
| } |
| |
| ASTNodes.replaceButKeepComment(rewrite, arrayCreation, newAtomicObject, groupdeclaration); |
| |
| for (Object variableDimension : variableDimensions) { |
| rewrite.remove((ASTNode) variableDimension, groupdeclaration); |
| } |
| |
| Type atomicType= ast.newSimpleType(ASTNodeFactory.newName(ast, atomicClassNameText)); |
| |
| if (objectClass != null) { |
| ParameterizedType newParameterizedType= ast.newParameterizedType(atomicType); |
| newParameterizedType.typeArguments().add(rewrite.createCopyTarget(objectClass)); |
| atomicInstance= newParameterizedType; |
| } |
| |
| ASTNodes.replaceButKeepComment(rewrite, type, atomicInstance, groupdeclaration); |
| |
| int i=1; |
| for (ArrayAccess accessRead : accessReads) { |
| MethodInvocation newMethodInvocation= ast.newMethodInvocation(); |
| newMethodInvocation.setExpression(ASTNodes.createMoveTarget(rewrite, accessRead.getArray())); |
| newMethodInvocation.setName(ast.newSimpleName("get")); //$NON-NLS-1$ |
| TextEditGroup groupusage= createTextEditGroup(MultiFixMessages.CodeStyleCleanUp_AtomicObject_usage+" "+Integer.toString(i++), cuRewrite); //$NON-NLS-1$ |
| ASTNodes.replaceButKeepComment(rewrite, accessRead, newMethodInvocation, groupusage); |
| } |
| |
| for (Assignment assignmentRead : assignmentReads) { |
| MethodInvocation newMethodInvocation= ast.newMethodInvocation(); |
| newMethodInvocation.setExpression(ASTNodes.createMoveTarget(rewrite, ((ArrayAccess) assignmentRead.getLeftHandSide()).getArray())); |
| newMethodInvocation.setName(ast.newSimpleName("set")); //$NON-NLS-1$ |
| newMethodInvocation.arguments().add(ASTNodes.createMoveTarget(rewrite, assignmentRead.getRightHandSide())); |
| TextEditGroup groupusage= createTextEditGroup(MultiFixMessages.CodeStyleCleanUp_AtomicObject_usage+" "+Integer.toString(i++), cuRewrite); //$NON-NLS-1$ |
| ASTNodes.replaceButKeepComment(rewrite, assignmentRead, newMethodInvocation, groupusage); |
| } |
| } |
| } |
| } |