1 /******************************************************************************* 2 * Copyright (c) 2019 Fabrice TIERCELIN and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * Fabrice TIERCELIN - initial API and implementation 13 *******************************************************************************/ 14 package org.eclipse.jdt.internal.ui.fix; 15 16 import java.util.ArrayList; 17 import java.util.Collections; 18 import java.util.List; 19 import java.util.ListIterator; 20 import java.util.Map; 21 22 import org.eclipse.core.runtime.CoreException; 23 24 import org.eclipse.jdt.core.ICompilationUnit; 25 import org.eclipse.jdt.core.dom.AST; 26 import org.eclipse.jdt.core.dom.ASTMatcher; 27 import org.eclipse.jdt.core.dom.ASTNode; 28 import org.eclipse.jdt.core.dom.ASTVisitor; 29 import org.eclipse.jdt.core.dom.CompilationUnit; 30 import org.eclipse.jdt.core.dom.Expression; 31 import org.eclipse.jdt.core.dom.InfixExpression; 32 import org.eclipse.jdt.core.dom.InfixExpression.Operator; 33 import org.eclipse.jdt.core.dom.InstanceofExpression; 34 import org.eclipse.jdt.core.dom.ParenthesizedExpression; 35 import org.eclipse.jdt.core.dom.PrefixExpression; 36 import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; 37 38 import org.eclipse.jdt.internal.corext.dom.ASTNodes; 39 import org.eclipse.jdt.internal.corext.fix.CleanUpConstants; 40 import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix; 41 import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix.CompilationUnitRewriteOperation; 42 import org.eclipse.jdt.internal.corext.fix.LinkedProposalModel; 43 import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite; 44 45 import org.eclipse.jdt.ui.cleanup.CleanUpRequirements; 46 import org.eclipse.jdt.ui.cleanup.ICleanUpFix; 47 import org.eclipse.jdt.ui.text.java.IProblemLocation; 48 49 /** 50 * A fix that pushes down the negation into a boolean expression: 51 * <ul> 52 * <li>Removes double negations,</li> 53 * <li>Uses opposite boolean constants,</li> 54 * <li>Reverses arithmetic expressions.</li> 55 * </ul> 56 */ 57 public class PushDownNegationCleanUp extends AbstractMultiFix { PushDownNegationCleanUp()58 public PushDownNegationCleanUp() { 59 this(Collections.emptyMap()); 60 } 61 PushDownNegationCleanUp(Map<String, String> options)62 public PushDownNegationCleanUp(Map<String, String> options) { 63 super(options); 64 } 65 66 @Override getRequirements()67 public CleanUpRequirements getRequirements() { 68 boolean requireAST= isEnabled(CleanUpConstants.PUSH_DOWN_NEGATION); 69 Map<String, String> requiredOptions= null; 70 return new CleanUpRequirements(requireAST, false, false, requiredOptions); 71 } 72 73 @Override getStepDescriptions()74 public String[] getStepDescriptions() { 75 if (isEnabled(CleanUpConstants.PUSH_DOWN_NEGATION)) { 76 return new String[] { MultiFixMessages.PushDownNegationCleanup_description }; 77 } 78 return new String[0]; 79 } 80 81 @Override getPreview()82 public String getPreview() { 83 StringBuilder bld= new StringBuilder(); 84 if (isEnabled(CleanUpConstants.PUSH_DOWN_NEGATION)) { 85 bld.append("boolean b = (myInt <= 0);\n"); //$NON-NLS-1$ 86 bld.append("boolean b2 = (!isEnabled && !isValid);\n"); //$NON-NLS-1$ 87 } else { 88 bld.append("boolean b = !(myInt > 0);\n"); //$NON-NLS-1$ 89 bld.append("boolean b2 = !(isEnabled || isValid);\n"); //$NON-NLS-1$ 90 } 91 92 return bld.toString(); 93 } 94 95 @Override createFix(CompilationUnit unit)96 protected ICleanUpFix createFix(CompilationUnit unit) throws CoreException { 97 if (!isEnabled(CleanUpConstants.PUSH_DOWN_NEGATION)) { 98 return null; 99 } 100 101 final List<CompilationUnitRewriteOperation> rewriteOperations= new ArrayList<>(); 102 103 unit.accept(new ASTVisitor() { 104 PrefixExpression secondNotOperator= null; 105 @Override 106 public boolean visit(PrefixExpression node) { 107 if (!ASTNodes.hasOperator(node, PrefixExpression.Operator.NOT)) { 108 return true; 109 } 110 111 if (node.subtreeMatch(new ASTMatcher(), secondNotOperator)) { 112 // already processed as part of RemoveDoubleNegationOperation 113 return true; 114 } 115 116 return pushDown(node, node.getOperand()); 117 } 118 119 private boolean pushDown(final PrefixExpression node, Expression operand) { 120 operand= ASTNodes.getUnparenthesedExpression(operand); 121 122 if (operand instanceof PrefixExpression) { 123 final PrefixExpression pe= (PrefixExpression) operand; 124 125 if (ASTNodes.hasOperator(pe, PrefixExpression.Operator.NOT)) { 126 rewriteOperations.add(new RemoveDoubleNegationOperation(node, pe.getOperand())); 127 secondNotOperator= pe; 128 return true; 129 } 130 } else if (operand instanceof InfixExpression) { 131 final InfixExpression ie= (InfixExpression) operand; 132 final InfixExpression.Operator reverseOp= ASTNodes.oppositeInfixOperator(ie.getOperator()); 133 134 if (reverseOp != null) { 135 rewriteOperations.add(new PushDownNegationInInfixExpressionOperation(node, ie, reverseOp)); 136 return false; 137 } 138 } else { 139 final Boolean constant= ASTNodes.getBooleanLiteral(operand); 140 141 if (constant != null) { 142 rewriteOperations.add(new ReverseBooleanConstantOperation(node, !constant.booleanValue())); 143 return false; 144 } 145 } 146 147 return true; 148 } 149 }); 150 151 if (rewriteOperations.isEmpty()) { 152 return null; 153 } 154 155 RemoveDoubleNegationOperation lastDoubleNegation= null; 156 for (CompilationUnitRewriteOperation op : rewriteOperations) { 157 if (op instanceof ReplacementOperation) { 158 ReplacementOperation chainedOp= (ReplacementOperation) op; 159 if (lastDoubleNegation != null && chainedOp.getNode().subtreeMatch(new ASTMatcher(), lastDoubleNegation.getReplacementExpression())) { 160 lastDoubleNegation.setNextOperation(chainedOp); 161 } 162 if (op instanceof RemoveDoubleNegationOperation) { 163 lastDoubleNegation= (RemoveDoubleNegationOperation) op; 164 } 165 } 166 } 167 168 return new CompilationUnitRewriteOperationsFix(MultiFixMessages.PushDownNegationCleanup_description, unit, 169 rewriteOperations.toArray(new CompilationUnitRewriteOperation[rewriteOperations.size()])); 170 } 171 172 @Override canFix(ICompilationUnit compilationUnit, IProblemLocation problem)173 public boolean canFix(ICompilationUnit compilationUnit, IProblemLocation problem) { 174 return false; 175 } 176 177 @Override createFix(CompilationUnit unit, IProblemLocation[] problems)178 protected ICleanUpFix createFix(CompilationUnit unit, IProblemLocation[] problems) throws CoreException { 179 return null; 180 } 181 182 private abstract static class ReplacementOperation extends CompilationUnitRewriteOperation { 183 private ASTNode node; 184 setNode(ASTNode node)185 public void setNode(ASTNode node) { 186 this.node= node; 187 } 188 getNode()189 public ASTNode getNode() { 190 return this.node; 191 } 192 } 193 194 private static class RemoveDoubleNegationOperation extends ReplacementOperation { 195 private Expression replacement; 196 197 private ReplacementOperation nextOperation; 198 RemoveDoubleNegationOperation(ASTNode node, Expression replacement)199 public RemoveDoubleNegationOperation(ASTNode node, Expression replacement) { 200 this.setNode(node); 201 this.replacement= replacement; 202 } 203 204 @Override rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModel linkedModel)205 public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModel linkedModel) throws CoreException { 206 ASTRewrite rewrite= cuRewrite.getASTRewrite(); 207 Expression copyOfReplacement= (Expression) rewrite.createCopyTarget(this.replacement); 208 209 // if next operation has been replaced above by a copy, update the target node to change 210 if (nextOperation != null) { 211 nextOperation.setNode(copyOfReplacement); 212 } 213 214 rewrite.replace(this.getNode(), copyOfReplacement, null); 215 } 216 setNextOperation(ReplacementOperation nextOperation)217 public void setNextOperation(ReplacementOperation nextOperation) { 218 this.nextOperation= nextOperation; 219 } 220 getReplacementExpression()221 public Expression getReplacementExpression() { 222 return this.replacement; 223 } 224 } 225 226 private static class ReverseBooleanConstantOperation extends ReplacementOperation { 227 private boolean replacement; 228 ReverseBooleanConstantOperation(ASTNode node, boolean replacement)229 public ReverseBooleanConstantOperation(ASTNode node, boolean replacement) { 230 this.setNode(node); 231 this.replacement= replacement; 232 } 233 234 @Override rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModel linkedModel)235 public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModel linkedModel) throws CoreException { 236 ASTRewrite rewrite= cuRewrite.getASTRewrite(); 237 AST ast= cuRewrite.getRoot().getAST(); 238 Expression copyOfReplacement= ast.newBooleanLiteral(this.replacement); 239 240 rewrite.replace(this.getNode(), copyOfReplacement, null); 241 } 242 } 243 244 private static class PushDownNegationInInfixExpressionOperation extends ReplacementOperation { 245 private InfixExpression infixExpression; 246 247 private final Operator reverseOp; 248 PushDownNegationInInfixExpressionOperation(ASTNode node, InfixExpression infixExpression, Operator reverseOp)249 public PushDownNegationInInfixExpressionOperation(ASTNode node, InfixExpression infixExpression, Operator reverseOp) { 250 this.setNode(node); 251 this.infixExpression= infixExpression; 252 this.reverseOp= reverseOp; 253 } 254 255 @Override rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModel linkedModel)256 public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModel linkedModel) throws CoreException { 257 ASTRewrite rewrite= cuRewrite.getASTRewrite(); 258 AST ast= cuRewrite.getRoot().getAST(); 259 ParenthesizedExpression parenthesizedExpression= doRewriteAST(rewrite, ast, infixExpression, reverseOp); 260 261 rewrite.replace(this.getNode(), parenthesizedExpression, null); 262 } 263 doRewriteAST(ASTRewrite rewrite, AST ast, InfixExpression pInfixExpression, Operator pReverseOp)264 private ParenthesizedExpression doRewriteAST(ASTRewrite rewrite, AST ast, InfixExpression pInfixExpression, Operator pReverseOp) { 265 List<Expression> allOperands= new ArrayList<>(ASTNodes.allOperands(pInfixExpression)); 266 267 if (ASTNodes.hasOperator(pInfixExpression, InfixExpression.Operator.CONDITIONAL_AND, InfixExpression.Operator.CONDITIONAL_OR, InfixExpression.Operator.AND, 268 InfixExpression.Operator.OR)) { 269 for (ListIterator<Expression> it= allOperands.listIterator(); it.hasNext();) { 270 final Expression anOperand= it.next(); 271 final Expression oppositeExpression= getCopyOfOppositeExpression(rewrite, ast, anOperand); 272 273 if (oppositeExpression != null) { 274 it.set(oppositeExpression); 275 } else { 276 PrefixExpression prefixExpression= ast.newPrefixExpression(); 277 prefixExpression.setOperator(PrefixExpression.Operator.NOT); 278 if (anOperand instanceof InstanceofExpression) { 279 ParenthesizedExpression parenExpression= ast.newParenthesizedExpression(); 280 parenExpression.setExpression((Expression) rewrite.createCopyTarget(anOperand)); 281 prefixExpression.setOperand(parenExpression); 282 } else { 283 prefixExpression.setOperand((Expression) rewrite.createCopyTarget(anOperand)); 284 } 285 286 it.set(prefixExpression); 287 } 288 } 289 } else { 290 for (ListIterator<Expression> it= allOperands.listIterator(); it.hasNext();) { 291 it.set((Expression) rewrite.createCopyTarget(it.next())); 292 } 293 } 294 295 InfixExpression newIe= ast.newInfixExpression(); 296 List<Expression> copyOfAllOperands= new ArrayList<>(allOperands); 297 newIe.setOperator(pReverseOp); 298 newIe.setLeftOperand(copyOfAllOperands.remove(0)); 299 newIe.setRightOperand(copyOfAllOperands.remove(0)); 300 newIe.extendedOperands().addAll(copyOfAllOperands); 301 302 ParenthesizedExpression parenthesizedExpression= ast.newParenthesizedExpression(); 303 parenthesizedExpression.setExpression(newIe); 304 return parenthesizedExpression; 305 } 306 getCopyOfOppositeExpression(ASTRewrite rewrite, AST ast, final Expression operand)307 private Expression getCopyOfOppositeExpression(ASTRewrite rewrite, AST ast, final Expression operand) { 308 if (operand instanceof ParenthesizedExpression) { 309 return getCopyOfOppositeExpression(rewrite, ast, ((ParenthesizedExpression) operand).getExpression()); 310 } 311 312 if (operand instanceof PrefixExpression) { 313 final PrefixExpression pe= (PrefixExpression) operand; 314 315 if (ASTNodes.hasOperator(pe, PrefixExpression.Operator.NOT)) { 316 Expression otherOperand= pe.getOperand(); 317 PrefixExpression otherPe= ASTNodes.as(otherOperand, PrefixExpression.class); 318 319 if (otherPe != null && ASTNodes.hasOperator(otherPe, PrefixExpression.Operator.NOT)) { 320 return getCopyOfOppositeExpression(rewrite, ast, otherPe.getOperand()); 321 } 322 323 return (Expression) rewrite.createCopyTarget(otherOperand); 324 } 325 } else if (operand instanceof InfixExpression) { 326 final InfixExpression ie= (InfixExpression) operand; 327 final InfixExpression.Operator aReverseOp= ASTNodes.oppositeInfixOperator(ie.getOperator()); 328 329 if (aReverseOp != null) { 330 return doRewriteAST(rewrite, ast, ie, aReverseOp); 331 } 332 } else { 333 final Boolean constant= ASTNodes.getBooleanLiteral(operand); 334 335 if (constant != null) { 336 return ast.newBooleanLiteral(!constant.booleanValue()); 337 } 338 } 339 340 return null; 341 } 342 } 343 } 344