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