1 /*******************************************************************************
2  * Copyright (c) 2000, 2019 IBM Corporation 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  *   Konstantin Scheglov (scheglov_ke@nlmk.ru) - initial API and implementation
13  *          (reports 71244 & 74746: New Quick Assist's [quick assist])
14  *   IBM Corporation - implementation
15  *   Billy Huang <billyhuang31@gmail.com> - [quick assist] concatenate/merge string literals - https://bugs.eclipse.org/77632
16  *******************************************************************************/
17 package org.eclipse.jdt.internal.ui.text.correction;
18 
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.Collection;
22 import java.util.HashSet;
23 import java.util.Hashtable;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.ListIterator;
27 import java.util.Map;
28 
29 import org.eclipse.swt.graphics.Image;
30 
31 import org.eclipse.core.runtime.CoreException;
32 
33 import org.eclipse.jdt.core.ICompilationUnit;
34 import org.eclipse.jdt.core.IJavaProject;
35 import org.eclipse.jdt.core.NamingConventions;
36 import org.eclipse.jdt.core.dom.AST;
37 import org.eclipse.jdt.core.dom.ASTMatcher;
38 import org.eclipse.jdt.core.dom.ASTNode;
39 import org.eclipse.jdt.core.dom.ASTVisitor;
40 import org.eclipse.jdt.core.dom.AssertStatement;
41 import org.eclipse.jdt.core.dom.Assignment;
42 import org.eclipse.jdt.core.dom.Block;
43 import org.eclipse.jdt.core.dom.BodyDeclaration;
44 import org.eclipse.jdt.core.dom.BooleanLiteral;
45 import org.eclipse.jdt.core.dom.BreakStatement;
46 import org.eclipse.jdt.core.dom.CastExpression;
47 import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor;
48 import org.eclipse.jdt.core.dom.ClassInstanceCreation;
49 import org.eclipse.jdt.core.dom.CompilationUnit;
50 import org.eclipse.jdt.core.dom.ConditionalExpression;
51 import org.eclipse.jdt.core.dom.ConstructorInvocation;
52 import org.eclipse.jdt.core.dom.ContinueStatement;
53 import org.eclipse.jdt.core.dom.DoStatement;
54 import org.eclipse.jdt.core.dom.EnhancedForStatement;
55 import org.eclipse.jdt.core.dom.EnumConstantDeclaration;
56 import org.eclipse.jdt.core.dom.Expression;
57 import org.eclipse.jdt.core.dom.ExpressionStatement;
58 import org.eclipse.jdt.core.dom.ForStatement;
59 import org.eclipse.jdt.core.dom.IBinding;
60 import org.eclipse.jdt.core.dom.IMethodBinding;
61 import org.eclipse.jdt.core.dom.ITypeBinding;
62 import org.eclipse.jdt.core.dom.IVariableBinding;
63 import org.eclipse.jdt.core.dom.IfStatement;
64 import org.eclipse.jdt.core.dom.InfixExpression;
65 import org.eclipse.jdt.core.dom.InfixExpression.Operator;
66 import org.eclipse.jdt.core.dom.InstanceofExpression;
67 import org.eclipse.jdt.core.dom.LambdaExpression;
68 import org.eclipse.jdt.core.dom.MethodDeclaration;
69 import org.eclipse.jdt.core.dom.MethodInvocation;
70 import org.eclipse.jdt.core.dom.Modifier;
71 import org.eclipse.jdt.core.dom.Name;
72 import org.eclipse.jdt.core.dom.ParenthesizedExpression;
73 import org.eclipse.jdt.core.dom.PrefixExpression;
74 import org.eclipse.jdt.core.dom.PrimitiveType;
75 import org.eclipse.jdt.core.dom.QualifiedName;
76 import org.eclipse.jdt.core.dom.ReturnStatement;
77 import org.eclipse.jdt.core.dom.SimpleName;
78 import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
79 import org.eclipse.jdt.core.dom.Statement;
80 import org.eclipse.jdt.core.dom.StringLiteral;
81 import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor;
82 import org.eclipse.jdt.core.dom.SuperConstructorInvocation;
83 import org.eclipse.jdt.core.dom.SuperMethodInvocation;
84 import org.eclipse.jdt.core.dom.SwitchCase;
85 import org.eclipse.jdt.core.dom.SwitchStatement;
86 import org.eclipse.jdt.core.dom.ThrowStatement;
87 import org.eclipse.jdt.core.dom.Type;
88 import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
89 import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
90 import org.eclipse.jdt.core.dom.WhileStatement;
91 import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
92 import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
93 import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext;
94 import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.TypeLocation;
95 import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
96 
97 import org.eclipse.jdt.internal.core.manipulation.StubUtility;
98 import org.eclipse.jdt.internal.core.manipulation.dom.ASTResolving;
99 import org.eclipse.jdt.internal.core.manipulation.dom.NecessaryParenthesesChecker;
100 import org.eclipse.jdt.internal.core.manipulation.dom.OperatorPrecedence;
101 import org.eclipse.jdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext;
102 import org.eclipse.jdt.internal.corext.dom.ASTNodes;
103 import org.eclipse.jdt.internal.corext.dom.GenericVisitor;
104 import org.eclipse.jdt.internal.corext.dom.LinkedNodeFinder;
105 import org.eclipse.jdt.internal.corext.dom.StatementRewrite;
106 import org.eclipse.jdt.internal.corext.fix.CleanUpConstants;
107 import org.eclipse.jdt.internal.corext.fix.ExpressionsFix;
108 import org.eclipse.jdt.internal.corext.fix.IProposableFix;
109 import org.eclipse.jdt.internal.corext.refactoring.code.Invocations;
110 import org.eclipse.jdt.internal.corext.refactoring.util.NoCommentSourceRangeComputer;
111 import org.eclipse.jdt.internal.corext.refactoring.util.TightSourceRangeComputer;
112 import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
113 import org.eclipse.jdt.internal.corext.util.Messages;
114 
115 import org.eclipse.jdt.ui.cleanup.CleanUpOptions;
116 import org.eclipse.jdt.ui.text.java.IInvocationContext;
117 import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal;
118 import org.eclipse.jdt.ui.text.java.IProblemLocation;
119 import org.eclipse.jdt.ui.text.java.IQuickAssistProcessor;
120 import org.eclipse.jdt.ui.text.java.correction.ASTRewriteCorrectionProposal;
121 import org.eclipse.jdt.ui.text.java.correction.ICommandAccess;
122 
123 import org.eclipse.jdt.internal.ui.JavaPluginImages;
124 import org.eclipse.jdt.internal.ui.fix.ExpressionsCleanUp;
125 import org.eclipse.jdt.internal.ui.text.correction.proposals.FixCorrectionProposal;
126 import org.eclipse.jdt.internal.ui.text.correction.proposals.LinkedCorrectionProposal;
127 import org.eclipse.jdt.internal.ui.util.ASTHelper;
128 
129 /**
130  */
131 public class AdvancedQuickAssistProcessor implements IQuickAssistProcessor {
AdvancedQuickAssistProcessor()132 	public AdvancedQuickAssistProcessor() {
133 		super();
134 	}
135 
136 	@Override
hasAssists(IInvocationContext context)137 	public boolean hasAssists(IInvocationContext context) throws CoreException {
138 		ASTNode coveringNode= context.getCoveringNode();
139 		if (coveringNode != null) {
140 			ArrayList<ASTNode> coveredNodes= getFullyCoveredNodes(context, coveringNode);
141 			return getConvertToIfReturnProposals(context, coveringNode, null)
142 					|| getInverseIfProposals(context, coveringNode, null)
143 					|| getIfReturnIntoIfElseAtEndOfVoidMethodProposals(context, coveringNode, null)
144 					|| getInverseIfContinueIntoIfThenInLoopsProposals(context, coveringNode, null)
145 					|| getInverseIfIntoContinueInLoopsProposals(context, coveringNode, null)
146 					|| getInverseConditionProposals(context, coveringNode, coveredNodes, null)
147 					|| getRemoveExtraParenthesesProposals(context, coveringNode, coveredNodes, null)
148 					|| getAddParanoidalParenthesesProposals(context, coveredNodes, null)
149 					|| getAddParenthesesForExpressionProposals(context, coveringNode, null)
150 					|| getJoinAndIfStatementsProposals(context, coveringNode, null)
151 					|| getSplitAndConditionProposals(context, coveringNode, null)
152 					|| getJoinOrIfStatementsProposals(context, coveringNode, coveredNodes, null)
153 					|| getSplitOrConditionProposals(context, coveringNode, null)
154 					|| getInverseConditionalExpressionProposals(context, coveringNode, null)
155 					|| getExchangeInnerAndOuterIfConditionsProposals(context, coveringNode, null)
156 					|| getExchangeOperandsProposals(context, coveringNode, null)
157 					|| getCastAndAssignIfStatementProposals(context, coveringNode, null)
158 					|| getCombineStringProposals(context, coveringNode, null)
159 					|| getPickOutStringProposals(context, coveringNode, null)
160 					|| getReplaceIfElseWithConditionalProposals(context, coveringNode, null)
161 					|| getReplaceConditionalWithIfElseProposals(context, coveringNode, null)
162 					|| getInverseLocalVariableProposals(context, coveringNode, null)
163 					|| getPushNegationDownProposals(context, coveringNode, null)
164 					|| getPullNegationUpProposals(context, coveredNodes, null)
165 					|| getJoinIfListInIfElseIfProposals(context, coveringNode, coveredNodes, null)
166 					|| getConvertSwitchToIfProposals(context, coveringNode, null)
167 					|| getConvertIfElseToSwitchProposals(context, coveringNode, null)
168 					|| GetterSetterCorrectionSubProcessor.addGetterSetterProposal(context, coveringNode, null, null);
169 		}
170 		return false;
171 	}
172 
173 	@Override
getAssists(IInvocationContext context, IProblemLocation[] locations)174 	public IJavaCompletionProposal[] getAssists(IInvocationContext context, IProblemLocation[] locations) throws CoreException {
175 		ASTNode coveringNode= context.getCoveringNode();
176 		if (coveringNode != null) {
177 			ArrayList<ASTNode> coveredNodes= getFullyCoveredNodes(context, coveringNode);
178 			ArrayList<ICommandAccess> resultingCollections= new ArrayList<>();
179 
180 			//quick assists that show up also if there is an error/warning
181 			getReplaceConditionalWithIfElseProposals(context, coveringNode, resultingCollections);
182 
183 			if (QuickAssistProcessor.noErrorsAtLocation(locations)) {
184 				getConvertToIfReturnProposals(context, coveringNode, resultingCollections);
185 				getInverseIfProposals(context, coveringNode, resultingCollections);
186 				getIfReturnIntoIfElseAtEndOfVoidMethodProposals(context, coveringNode, resultingCollections);
187 				getInverseIfContinueIntoIfThenInLoopsProposals(context, coveringNode, resultingCollections);
188 				getInverseIfIntoContinueInLoopsProposals(context, coveringNode, resultingCollections);
189 				getInverseConditionProposals(context, coveringNode, coveredNodes, resultingCollections);
190 				getRemoveExtraParenthesesProposals(context, coveringNode, coveredNodes, resultingCollections);
191 				getAddParanoidalParenthesesProposals(context, coveredNodes, resultingCollections);
192 				getAddParenthesesForExpressionProposals(context, coveringNode, resultingCollections);
193 				getJoinAndIfStatementsProposals(context, coveringNode, resultingCollections);
194 				getSplitAndConditionProposals(context, coveringNode, resultingCollections);
195 				getJoinOrIfStatementsProposals(context, coveringNode, coveredNodes, resultingCollections);
196 				getSplitOrConditionProposals(context, coveringNode, resultingCollections);
197 				getInverseConditionalExpressionProposals(context, coveringNode, resultingCollections);
198 				getExchangeInnerAndOuterIfConditionsProposals(context, coveringNode, resultingCollections);
199 				getExchangeOperandsProposals(context, coveringNode, resultingCollections);
200 				getCastAndAssignIfStatementProposals(context, coveringNode, resultingCollections);
201 				getCombineStringProposals(context, coveringNode, resultingCollections);
202 				getPickOutStringProposals(context, coveringNode, resultingCollections);
203 				getReplaceIfElseWithConditionalProposals(context, coveringNode, resultingCollections);
204 				getInverseLocalVariableProposals(context, coveringNode, resultingCollections);
205 				getPushNegationDownProposals(context, coveringNode, resultingCollections);
206 				getPullNegationUpProposals(context, coveredNodes, resultingCollections);
207 				getJoinIfListInIfElseIfProposals(context, coveringNode, coveredNodes, resultingCollections);
208 				getConvertSwitchToIfProposals(context, coveringNode, resultingCollections);
209 				getConvertIfElseToSwitchProposals(context, coveringNode, resultingCollections);
210 				GetterSetterCorrectionSubProcessor.addGetterSetterProposal(context, coveringNode, locations, resultingCollections);
211 			}
212 
213 			return resultingCollections.toArray(new IJavaCompletionProposal[resultingCollections.size()]);
214 		}
215 		return null;
216 	}
217 
getConvertToIfReturnProposals(IInvocationContext context, ASTNode coveringNode, ArrayList<ICommandAccess> resultingCollections)218 	private static boolean getConvertToIfReturnProposals(IInvocationContext context, ASTNode coveringNode, ArrayList<ICommandAccess> resultingCollections) {
219 		if (!(coveringNode instanceof IfStatement)) {
220 			return false;
221 		}
222 		IfStatement ifStatement= (IfStatement) coveringNode;
223 		if (ifStatement.getElseStatement() != null) {
224 			return false;
225 		}
226 
227 		// enclosing lambda or method should return 'void'
228 		LambdaExpression enclosingLambda= ASTResolving.findEnclosingLambdaExpression(ifStatement);
229 		if (enclosingLambda != null) {
230 			IMethodBinding lambdaMethodBinding= enclosingLambda.resolveMethodBinding();
231 			if (lambdaMethodBinding == null) {
232 				return false;
233 			}
234 			if (!(ifStatement.getAST().resolveWellKnownType("void").equals(lambdaMethodBinding.getReturnType()))) { //$NON-NLS-1$
235 				return false;
236 			}
237 		} else {
238 			MethodDeclaration coveringMethod= ASTResolving.findParentMethodDeclaration(ifStatement);
239 			if (coveringMethod == null) {
240 				return false;
241 			}
242 			Type returnType= coveringMethod.getReturnType2();
243 			if (!isVoid(returnType)) {
244 				return false;
245 			}
246 		}
247 
248 		// should be present in a block
249 		if (!(ifStatement.getParent() instanceof Block)) {
250 			return false;
251 		}
252 		// should have at least one statement in 'then' part other than 'return'
253 		Statement thenStatement= ifStatement.getThenStatement();
254 		if (thenStatement instanceof ReturnStatement) {
255 			return false;
256 		}
257 		if (thenStatement instanceof Block) {
258 			List<Statement> thenStatements= ((Block) thenStatement).statements();
259 			if (thenStatements.isEmpty() || (thenStatements.size() == 1 && (thenStatements.get(0) instanceof ReturnStatement))) {
260 				return false;
261 			}
262 		}
263 		// should have no further executable statement
264 		if (!isLastStatementInEnclosingMethodOrLambda(ifStatement)) {
265 			return false;
266 		}
267 		//  we could produce quick assist
268 		if (resultingCollections == null) {
269 			return true;
270 		}
271 
272 		AST ast= coveringNode.getAST();
273 		ASTRewrite rewrite= ASTRewrite.create(ast);
274 
275 		// create inverted 'if' statement
276 		Expression inversedExpression= getInversedExpression(rewrite, ifStatement.getExpression());
277 		IfStatement newIf= ast.newIfStatement();
278 		newIf.setExpression(inversedExpression);
279 		newIf.setThenStatement(ast.newReturnStatement());
280 		ListRewrite listRewriter= rewrite.getListRewrite(ifStatement.getParent(), (ChildListPropertyDescriptor) ifStatement.getLocationInParent());
281 		listRewriter.replace(ifStatement, newIf, null);
282 		// remove last 'return' in 'then' block
283 		ArrayList<Statement> statements= getUnwrappedStatements(ifStatement.getThenStatement());
284 		Statement lastStatement= statements.get(statements.size() - 1);
285 		if (lastStatement instanceof ReturnStatement) {
286 			statements.remove(lastStatement);
287 		}
288 		// add statements from 'then' to the end of block
289 		for (Statement statement : statements) {
290 			listRewriter.insertLast(rewrite.createMoveTarget(statement), null);
291 		}
292 
293 		// add correction proposal
294 		String label= CorrectionMessages.AdvancedQuickAssistProcessor_convertToIfReturn;
295 		Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
296 		ASTRewriteCorrectionProposal proposal= new ASTRewriteCorrectionProposal(label, context.getCompilationUnit(), rewrite, IProposalRelevance.CONVERT_TO_IF_RETURN, image);
297 		resultingCollections.add(proposal);
298 		return true;
299 	}
300 
isVoid(Type type)301 	private static boolean isVoid(Type type) {
302 		return type instanceof PrimitiveType && ((PrimitiveType) type).getPrimitiveTypeCode() == PrimitiveType.VOID;
303 	}
304 
isLastStatementInEnclosingMethodOrLambda(Statement statement)305 	private static boolean isLastStatementInEnclosingMethodOrLambda(Statement statement) {
306 		ASTNode currentStructure= statement;
307 		ASTNode currentParent= statement.getParent();
308 		while (!(currentParent instanceof MethodDeclaration) && !(currentParent instanceof LambdaExpression)) {
309 			// should not be in a loop
310 			if (currentParent instanceof ForStatement || currentParent instanceof EnhancedForStatement
311 					|| currentParent instanceof WhileStatement || currentParent instanceof DoStatement) {
312 				return false;
313 			}
314 			if (currentParent instanceof Block) {
315 				Block parentBlock= (Block) currentParent;
316 				if (parentBlock.statements().indexOf(currentStructure) != parentBlock.statements().size() - 1) { // not last statement in the block
317 					return false;
318 				}
319 			}
320 			currentStructure= currentParent;
321 			currentParent= currentParent.getParent();
322 		}
323 		return true;
324 	}
325 
getIfReturnIntoIfElseAtEndOfVoidMethodProposals(IInvocationContext context, ASTNode covering, Collection<ICommandAccess> resultingCollections)326 	private static boolean getIfReturnIntoIfElseAtEndOfVoidMethodProposals(IInvocationContext context, ASTNode covering, Collection<ICommandAccess> resultingCollections) {
327 		if (!(covering instanceof IfStatement)) {
328 			return false;
329 		}
330 		IfStatement ifStatement= (IfStatement) covering;
331 		if (ifStatement.getElseStatement() != null) {
332 			return false;
333 		}
334 		// 'then' block should have 'return' as last statement
335 		Statement thenStatement= ifStatement.getThenStatement();
336 		if (!(thenStatement instanceof Block)) {
337 			return false;
338 		}
339 		Block thenBlock= (Block) thenStatement;
340 		List<Statement> thenStatements= thenBlock.statements();
341 		if (thenStatements.isEmpty() || !(thenStatements.get(thenStatements.size() - 1) instanceof ReturnStatement)) {
342 			return false;
343 		}
344 		// method should return 'void'
345 		MethodDeclaration coveringMetod= ASTResolving.findParentMethodDeclaration(covering);
346 		if (coveringMetod == null) {
347 			return false;
348 		}
349 		Type returnType= coveringMetod.getReturnType2();
350 		if (!isVoid(returnType)) {
351 			return false;
352 		}
353 		//
354 		List<Statement> statements= coveringMetod.getBody().statements();
355 		int ifIndex= statements.indexOf(ifStatement);
356 		if (ifIndex == -1) {
357 			return false;
358 		}
359 		//  we could produce quick assist
360 		if (resultingCollections == null) {
361 			return true;
362 		}
363 		//
364 		AST ast= covering.getAST();
365 		ASTRewrite rewrite= ASTRewrite.create(ast);
366 		// remove last 'return' in 'then' block
367 		ListRewrite listRewriter= rewrite.getListRewrite(thenBlock, (ChildListPropertyDescriptor) ifStatement.getLocationInParent());
368 		listRewriter.remove(thenStatements.get(thenStatements.size() - 1), null);
369 		// prepare original nodes
370 		Expression conditionPlaceholder= (Expression) rewrite.createMoveTarget(ifStatement.getExpression());
371 		Statement thenPlaceholder= (Statement) rewrite.createMoveTarget(ifStatement.getThenStatement());
372 		// prepare 'else' block
373 		Block elseBlock= ast.newBlock();
374 		for (int i= ifIndex + 1; i < statements.size(); i++) {
375 			Statement statement= statements.get(i);
376 			elseBlock.statements().add(rewrite.createMoveTarget(statement));
377 		}
378 		// prepare new 'if' statement
379 		IfStatement newIf= ast.newIfStatement();
380 		newIf.setExpression(conditionPlaceholder);
381 		newIf.setThenStatement(thenPlaceholder);
382 		newIf.setElseStatement(elseBlock);
383 		rewrite.replace(ifStatement, newIf, null);
384 		// add correction proposal
385 		String label= CorrectionMessages.AdvancedQuickAssistProcessor_convertToIfElse_description;
386 		Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
387 		ASTRewriteCorrectionProposal proposal= new ASTRewriteCorrectionProposal(label, context.getCompilationUnit(), rewrite, IProposalRelevance.CONVERT_TO_IF_ELSE, image);
388 		resultingCollections.add(proposal);
389 		return true;
390 	}
391 
getInverseIfProposals(IInvocationContext context, ASTNode covering, Collection<ICommandAccess> resultingCollections)392 	private static boolean getInverseIfProposals(IInvocationContext context, ASTNode covering, Collection<ICommandAccess> resultingCollections) {
393 		if (!(covering instanceof IfStatement)) {
394 			return false;
395 		}
396 		IfStatement ifStatement= (IfStatement) covering;
397 		if (ifStatement.getElseStatement() == null) {
398 			return false;
399 		}
400 		//  we could produce quick assist
401 		if (resultingCollections == null) {
402 			return true;
403 		}
404 		//
405 		AST ast= covering.getAST();
406 		ASTRewrite rewrite= ASTRewrite.create(ast);
407 		Statement thenStatement= ifStatement.getThenStatement();
408 		Statement elseStatement= ifStatement.getElseStatement();
409 
410 		// prepare original nodes
411 		Expression inversedExpression= getInversedExpression(rewrite, ifStatement.getExpression());
412 
413 		Statement newElseStatement= (Statement) rewrite.createMoveTarget(thenStatement);
414 		Statement newThenStatement= (Statement) rewrite.createMoveTarget(elseStatement);
415 		// set new nodes
416 		rewrite.set(ifStatement, IfStatement.EXPRESSION_PROPERTY, inversedExpression, null);
417 
418 		if (elseStatement instanceof IfStatement) {// bug 79507 && bug 74580
419 			Block elseBlock= ast.newBlock();
420 			elseBlock.statements().add(newThenStatement);
421 			newThenStatement= elseBlock;
422 		}
423 		rewrite.set(ifStatement, IfStatement.THEN_STATEMENT_PROPERTY, newThenStatement, null);
424 		rewrite.set(ifStatement, IfStatement.ELSE_STATEMENT_PROPERTY, newElseStatement, null);
425 		// add correction proposal
426 		String label= CorrectionMessages.AdvancedQuickAssistProcessor_inverseIf_description;
427 		Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
428 		ASTRewriteCorrectionProposal proposal= new ASTRewriteCorrectionProposal(label, context.getCompilationUnit(), rewrite, IProposalRelevance.INVERSE_IF_STATEMENT, image);
429 		resultingCollections.add(proposal);
430 		return true;
431 	}
432 
getInverseIfContinueIntoIfThenInLoopsProposals(IInvocationContext context, ASTNode covering, Collection<ICommandAccess> resultingCollections)433 	private static boolean getInverseIfContinueIntoIfThenInLoopsProposals(IInvocationContext context, ASTNode covering, Collection<ICommandAccess> resultingCollections) {
434 		if (!(covering instanceof IfStatement)) {
435 			return false;
436 		}
437 		IfStatement ifStatement= (IfStatement) covering;
438 		if (ifStatement.getElseStatement() != null) {
439 			return false;
440 		}
441 		// check that 'then' is 'continue'
442 		if (!(ifStatement.getThenStatement() instanceof ContinueStatement)) {
443 			return false;
444 		}
445 		// check that 'if' statement is statement in block that is body of loop
446 		Block loopBlock= null;
447 		if (ifStatement.getParent() instanceof Block && ifStatement.getParent().getParent() instanceof ForStatement) {
448 			loopBlock= (Block) ifStatement.getParent();
449 		} else if (ifStatement.getParent() instanceof Block && ifStatement.getParent().getParent() instanceof WhileStatement) {
450 			loopBlock= (Block) ifStatement.getParent();
451 		} else {
452 			return false;
453 		}
454 		if (resultingCollections == null) {
455 			return true;
456 		}
457 		//
458 		AST ast= covering.getAST();
459 		ASTRewrite rewrite= ASTRewrite.create(ast);
460 		// create inverted 'if' statement
461 		Expression inversedExpression= getInversedExpression(rewrite, ifStatement.getExpression());
462 		IfStatement newIf= ast.newIfStatement();
463 		newIf.setExpression(inversedExpression);
464 		// prepare 'then' for new 'if'
465 		Block thenBlock= ast.newBlock();
466 		int ifIndex= loopBlock.statements().indexOf(ifStatement);
467 		for (int i= ifIndex + 1; i < loopBlock.statements().size(); i++) {
468 			Statement statement= (Statement) loopBlock.statements().get(i);
469 			thenBlock.statements().add(rewrite.createMoveTarget(statement));
470 		}
471 		newIf.setThenStatement(thenBlock);
472 		// replace 'if' statement in loop
473 		rewrite.replace(ifStatement, newIf, null);
474 		// add correction proposal
475 		String label= CorrectionMessages.AdvancedQuickAssistProcessor_inverseIfContinue_description;
476 		Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
477 		ASTRewriteCorrectionProposal proposal= new ASTRewriteCorrectionProposal(label, context.getCompilationUnit(), rewrite, IProposalRelevance.INVERSE_IF_CONTINUE, image);
478 		resultingCollections.add(proposal);
479 		return true;
480 	}
481 
getInverseIfIntoContinueInLoopsProposals(IInvocationContext context, ASTNode covering, Collection<ICommandAccess> resultingCollections)482 	private static boolean getInverseIfIntoContinueInLoopsProposals(IInvocationContext context, ASTNode covering, Collection<ICommandAccess> resultingCollections) {
483 		if (!(covering instanceof IfStatement)) {
484 			return false;
485 		}
486 		IfStatement ifStatement= (IfStatement) covering;
487 		if (ifStatement.getElseStatement() != null) {
488 			return false;
489 		}
490 		// prepare outer control structure and block that contains 'if' statement
491 		ASTNode ifParent= ifStatement.getParent();
492 		Block ifParentBlock= null;
493 		ASTNode ifParentStructure= ifParent;
494 		if (ifParentStructure instanceof Block) {
495 			ifParentBlock= (Block) ifParent;
496 			ifParentStructure= ifParentStructure.getParent();
497 		}
498 		// check that control structure is loop and 'if' statement if last statement
499 		if (!(ifParentStructure instanceof ForStatement) && !(ifParentStructure instanceof WhileStatement)) {
500 			return false;
501 		}
502 		if (ifParentBlock != null && ifParentBlock.statements().indexOf(ifStatement) != ifParentBlock.statements().size() - 1) {
503 			return false;
504 		}
505 		//  we could produce quick assist
506 		if (resultingCollections == null) {
507 			return true;
508 		}
509 		//
510 		AST ast= covering.getAST();
511 		ASTRewrite rewrite= ASTRewrite.create(ast);
512 		// create inverted 'if' statement
513 		Expression inversedExpression= getInversedExpression(rewrite, ifStatement.getExpression());
514 		IfStatement newIf= ast.newIfStatement();
515 		newIf.setExpression(inversedExpression);
516 		newIf.setThenStatement(ast.newContinueStatement());
517 		//
518 		if (ifParentBlock == null) {
519 			// if there is no block, create it
520 			ifParentBlock= ast.newBlock();
521 			ifParentBlock.statements().add(newIf);
522 			for (Statement statement : getUnwrappedStatements(ifStatement.getThenStatement())) {
523 				ifParentBlock.statements().add(rewrite.createMoveTarget(statement));
524 			}
525 			// replace 'if' statement as body with new block
526 			if (ifParentStructure instanceof ForStatement) {
527 				rewrite.set(ifParentStructure, ForStatement.BODY_PROPERTY, ifParentBlock, null);
528 			} else if (ifParentStructure instanceof WhileStatement) {
529 				rewrite.set(ifParentStructure, WhileStatement.BODY_PROPERTY, ifParentBlock, null);
530 			}
531 		} else {
532 			// if there was block, replace
533 			ListRewrite listRewriter= rewrite.getListRewrite(ifParentBlock, (ChildListPropertyDescriptor) ifStatement.getLocationInParent());
534 			listRewriter.replace(ifStatement, newIf, null);
535 			// add statements from 'then' to the end of block
536 			for (Statement statement : getUnwrappedStatements(ifStatement.getThenStatement())) {
537 				listRewriter.insertLast(rewrite.createMoveTarget(statement), null);
538 			}
539 		}
540 		// add correction proposal
541 		String label= CorrectionMessages.AdvancedQuickAssistProcessor_inverseIfToContinue_description;
542 		Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
543 		ASTRewriteCorrectionProposal proposal= new ASTRewriteCorrectionProposal(label, context.getCompilationUnit(), rewrite, IProposalRelevance.INVERT_IF_TO_CONTINUE, image);
544 		resultingCollections.add(proposal);
545 		return true;
546 	}
547 
getUnwrappedStatements(Statement body)548 	private static ArrayList<Statement> getUnwrappedStatements(Statement body) {
549 		ArrayList<Statement> statements= new ArrayList<>();
550 		if (body instanceof Block) {
551 			for (Iterator<Statement> iter= ((Block) body).statements().iterator(); iter.hasNext();) {
552 				Statement statement= iter.next();
553 				statements.add(statement);
554 			}
555 		} else {
556 			statements.add(body);
557 		}
558 		return statements;
559 	}
560 
getInverseConditionProposals(IInvocationContext context, ASTNode covering, ArrayList<ASTNode> coveredNodes, Collection<ICommandAccess> resultingCollections)561 	private static boolean getInverseConditionProposals(IInvocationContext context, ASTNode covering, ArrayList<ASTNode> coveredNodes, Collection<ICommandAccess> resultingCollections) {
562 		if (coveredNodes.isEmpty()) {
563 			return false;
564 		}
565 		//
566 		final AST ast= covering.getAST();
567 		final ASTRewrite rewrite= ASTRewrite.create(ast);
568 		// check sub-expressions in fully covered nodes
569 		boolean hasChanges= false;
570 		for (ASTNode covered : coveredNodes) {
571 			Expression coveredExpression= getBooleanExpression(covered);
572 			if (coveredExpression != null) {
573 				Expression inversedExpression= getInversedExpression(rewrite, coveredExpression);
574 				rewrite.replace(coveredExpression, inversedExpression, null);
575 				hasChanges= true;
576 			}
577 		}
578 		//
579 		if (!hasChanges) {
580 			return false;
581 		}
582 		if (resultingCollections == null) {
583 			return true;
584 		}
585 		// add correction proposal
586 		String label= CorrectionMessages.AdvancedQuickAssistProcessor_inverseConditions_description;
587 		Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
588 		ASTRewriteCorrectionProposal proposal= new ASTRewriteCorrectionProposal(label, context.getCompilationUnit(), rewrite, IProposalRelevance.INVERSE_CONDITIONS, image);
589 		resultingCollections.add(proposal);
590 		return true;
591 	}
592 
getInversedExpression(ASTRewrite rewrite, Expression expression)593 	private static Expression getInversedExpression(ASTRewrite rewrite, Expression expression) {
594 		return getInversedExpression(rewrite, expression, null);
595 	}
596 	private interface SimpleNameRenameProvider {
getRenamed(SimpleName name)597 		SimpleName getRenamed(SimpleName name);
598 	}
599 
getRenamedNameCopy(SimpleNameRenameProvider provider, ASTRewrite rewrite, Expression expression)600 	private static Expression getRenamedNameCopy(SimpleNameRenameProvider provider, ASTRewrite rewrite, Expression expression) {
601 		if (provider != null) {
602 			if (expression instanceof SimpleName) {
603 				SimpleName name= (SimpleName) expression;
604 				SimpleName newName= provider.getRenamed(name);
605 				if (newName != null) {
606 					return newName;
607 				}
608 			}
609 		}
610 		return (Expression) rewrite.createCopyTarget(expression);
611 	}
612 
getInversedExpression(ASTRewrite rewrite, Expression expression, SimpleNameRenameProvider provider)613 	private static Expression getInversedExpression(ASTRewrite rewrite, Expression expression, SimpleNameRenameProvider provider) {
614 		AST ast= rewrite.getAST();
615 		//
616 		if (expression instanceof BooleanLiteral) {
617 			return ast.newBooleanLiteral(!((BooleanLiteral) expression).booleanValue());
618 		}
619 		if (expression instanceof InfixExpression) {
620 			InfixExpression infixExpression= (InfixExpression) expression;
621 			InfixExpression.Operator operator= infixExpression.getOperator();
622 			if (operator == InfixExpression.Operator.LESS) {
623 				return getInversedInfixExpression(rewrite, infixExpression, InfixExpression.Operator.GREATER_EQUALS, provider);
624 			}
625 			if (operator == InfixExpression.Operator.GREATER) {
626 				return getInversedInfixExpression(rewrite, infixExpression, InfixExpression.Operator.LESS_EQUALS, provider);
627 			}
628 			if (operator == InfixExpression.Operator.LESS_EQUALS) {
629 				return getInversedInfixExpression(rewrite, infixExpression, InfixExpression.Operator.GREATER, provider);
630 			}
631 			if (operator == InfixExpression.Operator.GREATER_EQUALS) {
632 				return getInversedInfixExpression(rewrite, infixExpression, InfixExpression.Operator.LESS, provider);
633 			}
634 			if (operator == InfixExpression.Operator.EQUALS) {
635 				return getInversedInfixExpression(rewrite, infixExpression, InfixExpression.Operator.NOT_EQUALS, provider);
636 			}
637 			if (operator == InfixExpression.Operator.NOT_EQUALS) {
638 				return getInversedInfixExpression(rewrite, infixExpression, InfixExpression.Operator.EQUALS, provider);
639 			}
640 			if (operator == InfixExpression.Operator.CONDITIONAL_AND) {
641 				return getInversedAndOrExpression(rewrite, infixExpression, InfixExpression.Operator.CONDITIONAL_OR, provider);
642 			}
643 			if (operator == InfixExpression.Operator.CONDITIONAL_OR) {
644 				return getInversedAndOrExpression(rewrite, infixExpression, InfixExpression.Operator.CONDITIONAL_AND, provider);
645 			}
646 			if (operator == InfixExpression.Operator.AND) {
647 				return getInversedAndOrExpression(rewrite, infixExpression, InfixExpression.Operator.OR, provider);
648 			}
649 			if (operator == InfixExpression.Operator.OR) {
650 				return getInversedAndOrExpression(rewrite, infixExpression, InfixExpression.Operator.AND, provider);
651 			}
652 			if (operator == InfixExpression.Operator.XOR) {
653 				return getInversedNotExpression(rewrite, expression, ast);
654 			}
655 		}
656 		if (expression instanceof PrefixExpression) {
657 			PrefixExpression prefixExpression= (PrefixExpression) expression;
658 			if (prefixExpression.getOperator() == PrefixExpression.Operator.NOT) {
659 				Expression operand= prefixExpression.getOperand();
660 				if ((operand instanceof ParenthesizedExpression)
661 						&& NecessaryParenthesesChecker.canRemoveParentheses(operand, expression.getParent(), expression.getLocationInParent())) {
662 					operand= ((ParenthesizedExpression)operand).getExpression();
663 				}
664 				Expression renamedNameCopy= getRenamedNameCopy(provider, rewrite, operand);
665 				if (renamedNameCopy instanceof InfixExpression) {
666 					InfixExpression infixExpression= (InfixExpression) renamedNameCopy;
667 					infixExpression.setOperator(((InfixExpression) operand).getOperator());
668 				}
669 				return renamedNameCopy;
670 			}
671 		}
672 		if (expression instanceof InstanceofExpression) {
673 			return getInversedNotExpression(rewrite, expression, ast);
674 		}
675 		if (expression instanceof ParenthesizedExpression) {
676 			ParenthesizedExpression parenthesizedExpression= (ParenthesizedExpression) expression;
677 			Expression innerExpression= ASTNodes.getUnparenthesedExpression(parenthesizedExpression.getExpression());
678 			if (innerExpression instanceof InstanceofExpression) {
679 				return getInversedExpression(rewrite, innerExpression, provider);
680 			}
681 			parenthesizedExpression= getParenthesizedExpression(ast, getInversedExpression(rewrite, innerExpression, provider));
682 			return parenthesizedExpression;
683 		}
684 		if (expression instanceof ConditionalExpression) {
685 			ConditionalExpression conditionalExpression= (ConditionalExpression)expression;
686 			ConditionalExpression newExpression= ast.newConditionalExpression();
687 			newExpression.setExpression((Expression)rewrite.createCopyTarget(conditionalExpression.getExpression()));
688 			newExpression.setThenExpression(getInversedExpression(rewrite, conditionalExpression.getThenExpression()));
689 			newExpression.setElseExpression(getInversedExpression(rewrite, conditionalExpression.getElseExpression()));
690 			return newExpression;
691 		}
692 
693 		PrefixExpression prefixExpression= ast.newPrefixExpression();
694 		prefixExpression.setOperator(PrefixExpression.Operator.NOT);
695 		Expression renamedNameCopy= getRenamedNameCopy(provider, rewrite, expression);
696 		if (NecessaryParenthesesChecker.needsParentheses(renamedNameCopy, prefixExpression, PrefixExpression.OPERAND_PROPERTY)) {
697 			renamedNameCopy= getParenthesizedExpression(ast, renamedNameCopy);
698 		}
699 		prefixExpression.setOperand(renamedNameCopy);
700 		return prefixExpression;
701 	}
702 
getInversedNotExpression(ASTRewrite rewrite, Expression expression, AST ast)703 	private static Expression getInversedNotExpression(ASTRewrite rewrite, Expression expression, AST ast) {
704 		PrefixExpression prefixExpression= ast.newPrefixExpression();
705 		prefixExpression.setOperator(PrefixExpression.Operator.NOT);
706 		ParenthesizedExpression parenthesizedExpression= getParenthesizedExpression(ast, (Expression)rewrite.createCopyTarget(expression));
707 		prefixExpression.setOperand(parenthesizedExpression);
708 		return prefixExpression;
709 	}
710 
isBoolean(Expression expression)711 	private static boolean isBoolean(Expression expression) {
712 		ITypeBinding typeBinding= expression.resolveTypeBinding();
713 		AST ast= expression.getAST();
714 		return typeBinding == ast.resolveWellKnownType("boolean") //$NON-NLS-1$
715 				|| typeBinding == ast.resolveWellKnownType("java.lang.Boolean"); //$NON-NLS-1$
716 	}
717 
getInversedInfixExpression(ASTRewrite rewrite, InfixExpression expression, InfixExpression.Operator newOperator, SimpleNameRenameProvider provider)718 	private static Expression getInversedInfixExpression(ASTRewrite rewrite, InfixExpression expression, InfixExpression.Operator newOperator, SimpleNameRenameProvider provider) {
719 		InfixExpression newExpression= rewrite.getAST().newInfixExpression();
720 		newExpression.setOperator(newOperator);
721 		newExpression.setLeftOperand(getRenamedNameCopy(provider, rewrite, expression.getLeftOperand()));
722 		newExpression.setRightOperand(getRenamedNameCopy(provider, rewrite, expression.getRightOperand()));
723 		return newExpression;
724 	}
725 
parenthesizeIfRequired(Expression operand, int newOperatorPrecedence)726 	private static Expression parenthesizeIfRequired(Expression operand, int newOperatorPrecedence) {
727 		if (newOperatorPrecedence > OperatorPrecedence.getExpressionPrecedence(operand)) {
728 			return getParenthesizedExpression(operand.getAST(), operand);
729 		}
730 		return operand;
731 	}
732 
getInversedAndOrExpression(ASTRewrite rewrite, InfixExpression infixExpression, Operator newOperator, SimpleNameRenameProvider provider)733 	private static Expression getInversedAndOrExpression(ASTRewrite rewrite, InfixExpression infixExpression, Operator newOperator, SimpleNameRenameProvider provider) {
734 		InfixExpression newExpression= rewrite.getAST().newInfixExpression();
735 		newExpression.setOperator(newOperator);
736 
737 		int newOperatorPrecedence= OperatorPrecedence.getOperatorPrecedence(newOperator);
738 		//
739 		Expression leftOperand= getInversedExpression(rewrite, infixExpression.getLeftOperand(), provider);
740 		newExpression.setLeftOperand(parenthesizeIfRequired(leftOperand, newOperatorPrecedence));
741 
742 		Expression rightOperand= getInversedExpression(rewrite, infixExpression.getRightOperand(), provider);
743 		newExpression.setRightOperand(parenthesizeIfRequired(rightOperand, newOperatorPrecedence));
744 
745 		List<Expression> extraOperands= infixExpression.extendedOperands();
746 		List<Expression> newExtraOperands= newExpression.extendedOperands();
747 		for (int i= 0; i < extraOperands.size(); i++) {
748 			Expression extraOperand= getInversedExpression(rewrite, extraOperands.get(i), provider);
749 			newExtraOperands.add(parenthesizeIfRequired(extraOperand, newOperatorPrecedence));
750 		}
751 		return newExpression;
752 	}
753 
getRemoveExtraParenthesesProposals(IInvocationContext context, ASTNode covering, ArrayList<ASTNode> coveredNodes, Collection<ICommandAccess> resultingCollections)754 	private static boolean getRemoveExtraParenthesesProposals(IInvocationContext context, ASTNode covering, ArrayList<ASTNode> coveredNodes, Collection<ICommandAccess> resultingCollections) {
755 		ArrayList<ASTNode> nodes;
756 		if (context.getSelectionLength() == 0 && covering instanceof ParenthesizedExpression) {
757 			nodes= new ArrayList<>();
758 			nodes.add(covering);
759 		} else {
760 			nodes= coveredNodes;
761 		}
762 		if (nodes.isEmpty())
763 			return false;
764 
765 		IProposableFix fix= ExpressionsFix.createRemoveUnnecessaryParenthesisFix(context.getASTRoot(), nodes.toArray(new ASTNode[nodes.size()]));
766 		if (fix == null)
767 			return false;
768 
769 		if (resultingCollections == null)
770 			return true;
771 
772 		Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_REMOVE);
773 		Map<String, String> options= new Hashtable<>();
774 		options.put(CleanUpConstants.EXPRESSIONS_USE_PARENTHESES, CleanUpOptions.TRUE);
775 		options.put(CleanUpConstants.EXPRESSIONS_USE_PARENTHESES_NEVER, CleanUpOptions.TRUE);
776 		FixCorrectionProposal proposal= new FixCorrectionProposal(fix, new ExpressionsCleanUp(options), IProposalRelevance.REMOVE_EXTRA_PARENTHESES, image, context);
777 		resultingCollections.add(proposal);
778 		return true;
779 	}
780 
getAddParanoidalParenthesesProposals(IInvocationContext context, ArrayList<ASTNode> coveredNodes, Collection<ICommandAccess> resultingCollections)781 	private static boolean getAddParanoidalParenthesesProposals(IInvocationContext context, ArrayList<ASTNode> coveredNodes, Collection<ICommandAccess> resultingCollections) {
782 
783 		IProposableFix fix= ExpressionsFix.createAddParanoidalParenthesisFix(context.getASTRoot(), coveredNodes.toArray(new ASTNode[coveredNodes.size()]));
784 		if (fix == null)
785 			return false;
786 
787 		if (resultingCollections == null)
788 			return true;
789 
790 		// add correction proposal
791 		Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CAST);
792 		Map<String, String> options= new Hashtable<>();
793 		options.put(CleanUpConstants.EXPRESSIONS_USE_PARENTHESES, CleanUpOptions.TRUE);
794 		options.put(CleanUpConstants.EXPRESSIONS_USE_PARENTHESES_ALWAYS, CleanUpOptions.TRUE);
795 		FixCorrectionProposal proposal= new FixCorrectionProposal(fix, new ExpressionsCleanUp(options), IProposalRelevance.ADD_PARANOIDAL_PARENTHESES, image, context);
796 		resultingCollections.add(proposal);
797 		return true;
798 	}
799 
getAddParenthesesForExpressionProposals(IInvocationContext context, ASTNode coveringNode, Collection<ICommandAccess> resultingCollections)800 	private static boolean getAddParenthesesForExpressionProposals(IInvocationContext context, ASTNode coveringNode, Collection<ICommandAccess> resultingCollections) {
801 		ASTNode node;
802 
803 		if (context.getSelectionLength() == 0) {
804 			node= coveringNode;
805 			while (node != null && !(node instanceof CastExpression) && !(node instanceof InfixExpression) && !(node instanceof InstanceofExpression) && !(node instanceof ConditionalExpression)) {
806 				node= node.getParent();
807 			}
808 		} else {
809 			node= context.getCoveredNode();
810 		}
811 
812 		String label= null;
813 		if (node instanceof CastExpression) {
814 			label= CorrectionMessages.UnresolvedElementsSubProcessor_missingcastbrackets_description;
815 		} else if (node instanceof InstanceofExpression) {
816 			label= CorrectionMessages.LocalCorrectionsSubProcessor_setparenteses_instanceof_description;
817 		} else if (node instanceof InfixExpression) {
818 			InfixExpression infixExpression= (InfixExpression)node;
819 			label= Messages.format(CorrectionMessages.LocalCorrectionsSubProcessor_setparenteses_description, infixExpression.getOperator().toString());
820 		} else if (node instanceof ConditionalExpression) {
821 			label= CorrectionMessages.AdvancedQuickAssistProcessor_putConditionalExpressionInParentheses;
822 		} else {
823 			return false;
824 		}
825 
826 		if (node.getParent() instanceof ParenthesizedExpression)
827 			return false;
828 
829 		if (resultingCollections == null)
830 			return true;
831 
832 		AST ast= node.getAST();
833 		ASTRewrite rewrite= ASTRewrite.create(ast);
834 
835 		ParenthesizedExpression parenthesizedExpression= ast.newParenthesizedExpression();
836 		parenthesizedExpression.setExpression((Expression)rewrite.createCopyTarget(node));
837 		rewrite.replace(node, parenthesizedExpression, null);
838 
839 		Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CAST);
840 		ASTRewriteCorrectionProposal proposal= new ASTRewriteCorrectionProposal(label, context.getCompilationUnit(), rewrite, IProposalRelevance.ADD_PARENTHESES_FOR_EXPRESSION, image);
841 		resultingCollections.add(proposal);
842 		return true;
843 	}
844 
getFullyCoveredNodes(IInvocationContext context, ASTNode coveringNode)845 	static ArrayList<ASTNode> getFullyCoveredNodes(IInvocationContext context, ASTNode coveringNode) {
846 		final ArrayList<ASTNode> coveredNodes= new ArrayList<>();
847 		final int selectionBegin= context.getSelectionOffset();
848 		final int selectionEnd= selectionBegin + context.getSelectionLength();
849 		coveringNode.accept(new GenericVisitor() {
850 			@Override
851 			protected boolean visitNode(ASTNode node) {
852 				int nodeStart= node.getStartPosition();
853 				int nodeEnd= nodeStart + node.getLength();
854 				// if node does not intersects with selection, don't visit children
855 				if (nodeEnd < selectionBegin || selectionEnd < nodeStart) {
856 					return false;
857 				}
858 				// if node is fully covered, we don't need to visit children
859 				if (isCovered(node)) {
860 					ASTNode parent= node.getParent();
861 					if (parent == null || !isCovered(parent)) {
862 						coveredNodes.add(node);
863 						return false;
864 					}
865 				}
866 				// if node only partly intersects with selection, we try to find fully covered children
867 				return true;
868 			}
869 
870 			private boolean isCovered(ASTNode node) {
871 				int begin= node.getStartPosition();
872 				int end= begin + node.getLength();
873 				return begin >= selectionBegin && end <= selectionEnd;
874 			}
875 		});
876 		return coveredNodes;
877 	}
878 
getJoinAndIfStatementsProposals(IInvocationContext context, ASTNode node, Collection<ICommandAccess> resultingCollections)879 	private static boolean getJoinAndIfStatementsProposals(IInvocationContext context, ASTNode node, Collection<ICommandAccess> resultingCollections) {
880 
881 		//
882 		if (!(node instanceof IfStatement)) {
883 			return false;
884 		}
885 		IfStatement ifStatement= (IfStatement) node;
886 		if (ifStatement.getElseStatement() != null) {
887 			return false;
888 		}
889 		// case when current IfStatement is sole child of another IfStatement
890 		{
891 			IfStatement outerIf= null;
892 			if (ifStatement.getParent() instanceof IfStatement) {
893 				outerIf= (IfStatement) ifStatement.getParent();
894 			} else if (ifStatement.getParent() instanceof Block) {
895 				Block block= (Block) ifStatement.getParent();
896 				if (block.getParent() instanceof IfStatement && block.statements().size() == 1) {
897 					outerIf= (IfStatement) block.getParent();
898 				}
899 			}
900 			if (outerIf != null && outerIf.getElseStatement() == null) {
901 				if (resultingCollections == null) {
902 					return true;
903 				}
904 				//
905 				AST ast= node.getAST();
906 				ASTRewrite rewrite= ASTRewrite.create(ast);
907 				// create compound condition
908 				InfixExpression condition= ast.newInfixExpression();
909 				condition.setOperator(InfixExpression.Operator.CONDITIONAL_AND);
910 				// prepare condition parts, add parentheses if needed
911 				Expression outerCondition= getParenthesizedExpressionIfNeeded(ast, rewrite, outerIf.getExpression(), condition, InfixExpression.LEFT_OPERAND_PROPERTY);
912 				Expression innerCondition= getParenthesizedExpressionIfNeeded(ast, rewrite, ifStatement.getExpression(), condition, InfixExpression.RIGHT_OPERAND_PROPERTY);
913 				condition.setLeftOperand(outerCondition);
914 				condition.setRightOperand(innerCondition);
915 				// create new IfStatement
916 				IfStatement newIf= ast.newIfStatement();
917 				newIf.setExpression(condition);
918 				Statement bodyPlaceholder= (Statement) rewrite.createCopyTarget(ifStatement.getThenStatement());
919 				newIf.setThenStatement(bodyPlaceholder);
920 				rewrite.setTargetSourceRangeComputer(new NoCommentSourceRangeComputer());
921 				rewrite.replace(outerIf, newIf, null);
922 				// add correction proposal
923 				String label= CorrectionMessages.AdvancedQuickAssistProcessor_joinWithOuter_description;
924 				Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
925 				ASTRewriteCorrectionProposal proposal= new ASTRewriteCorrectionProposal(label, context.getCompilationUnit(), rewrite, IProposalRelevance.JOIN_IF_WITH_OUTER_IF, image);
926 				resultingCollections.add(proposal);
927 			}
928 		}
929 		// case when current IfStatement has another IfStatement as sole child
930 		{
931 			IfStatement innerIf= null;
932 			if (ifStatement.getThenStatement() instanceof IfStatement) {
933 				innerIf= (IfStatement) ifStatement.getThenStatement();
934 			} else if (ifStatement.getThenStatement() instanceof Block) {
935 				Block block= (Block) ifStatement.getThenStatement();
936 				if (block.statements().size() == 1 && block.statements().get(0) instanceof IfStatement) {
937 					innerIf= (IfStatement) block.statements().get(0);
938 				}
939 			}
940 			if (innerIf != null && innerIf.getElseStatement() == null) {
941 				if (resultingCollections == null) {
942 					return true;
943 				}
944 				//
945 				AST ast= node.getAST();
946 				ASTRewrite rewrite= ASTRewrite.create(ast);
947 				// create compound condition
948 				InfixExpression condition= ast.newInfixExpression();
949 				condition.setOperator(InfixExpression.Operator.CONDITIONAL_AND);
950 				// prepare condition parts, add parentheses if needed
951 				Expression outerCondition= getParenthesizedExpressionIfNeeded(ast, rewrite, ifStatement.getExpression(), condition, InfixExpression.LEFT_OPERAND_PROPERTY);
952 				Expression innerCondition= getParenthesizedExpressionIfNeeded(ast, rewrite, innerIf.getExpression(), condition, InfixExpression.RIGHT_OPERAND_PROPERTY);
953 				condition.setLeftOperand(outerCondition);
954 				condition.setRightOperand(innerCondition);
955 				// create new IfStatement
956 				IfStatement newIf= ast.newIfStatement();
957 				newIf.setExpression(condition);
958 				Statement bodyPlaceholder= (Statement) rewrite.createCopyTarget(innerIf.getThenStatement());
959 				newIf.setThenStatement(bodyPlaceholder);
960 				rewrite.setTargetSourceRangeComputer(new NoCommentSourceRangeComputer());
961 				rewrite.replace(ifStatement, newIf, null);
962 				// add correction proposal
963 				String label= CorrectionMessages.AdvancedQuickAssistProcessor_joinWithInner_description;
964 				Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
965 				ASTRewriteCorrectionProposal proposal= new ASTRewriteCorrectionProposal(label, context.getCompilationUnit(), rewrite, IProposalRelevance.JOIN_IF_WITH_INNER_IF, image);
966 				resultingCollections.add(proposal);
967 			}
968 		}
969 		return true;
970 	}
971 
getParenthesizedExpressionIfNeeded(AST ast, ASTRewrite rewrite, Expression expression, ASTNode parent, StructuralPropertyDescriptor locationInParent)972 	private static Expression getParenthesizedExpressionIfNeeded(AST ast, ASTRewrite rewrite, Expression expression, ASTNode parent, StructuralPropertyDescriptor locationInParent) {
973 		boolean addParentheses= NecessaryParenthesesChecker.needsParentheses(expression, parent, locationInParent);
974 		expression= (Expression)rewrite.createCopyTarget(expression);
975 		if (addParentheses) {
976 			return getParenthesizedExpression(ast, expression);
977 		}
978 		return expression;
979 	}
980 
getParenthesizedExpression(AST ast, Expression expression)981 	private static ParenthesizedExpression getParenthesizedExpression(AST ast, Expression expression) {
982 		ParenthesizedExpression parenthesizedExpression= ast.newParenthesizedExpression();
983 		parenthesizedExpression.setExpression(expression);
984 		return parenthesizedExpression;
985 	}
986 
getSplitAndConditionProposals(IInvocationContext context, ASTNode node, Collection<ICommandAccess> resultingCollections)987 	public static boolean getSplitAndConditionProposals(IInvocationContext context, ASTNode node, Collection<ICommandAccess> resultingCollections) {
988 		Operator andOperator= InfixExpression.Operator.CONDITIONAL_AND;
989 		// check that user invokes quick assist on infix expression
990 		if (!(node instanceof InfixExpression)) {
991 			return false;
992 		}
993 		InfixExpression infixExpression= (InfixExpression) node;
994 		if (infixExpression.getOperator() != andOperator) {
995 			return false;
996 		}
997 		int offset= isOperatorSelected(infixExpression, context.getSelectionOffset(), context.getSelectionLength());
998 		if (offset == -1) {
999 			return false;
1000 		}
1001 
1002 		// check that infix expression belongs to IfStatement
1003 		Statement statement= ASTResolving.findParentStatement(node);
1004 		if (!(statement instanceof IfStatement)) {
1005 			return false;
1006 		}
1007 		IfStatement ifStatement= (IfStatement) statement;
1008 
1009 		// check that infix expression is part of first level && condition of IfStatement
1010 		InfixExpression topInfixExpression= infixExpression;
1011 		while (topInfixExpression.getParent() instanceof InfixExpression && ((InfixExpression) topInfixExpression.getParent()).getOperator() == andOperator) {
1012 			topInfixExpression= (InfixExpression) topInfixExpression.getParent();
1013 		}
1014 		if (ifStatement.getExpression() != topInfixExpression) {
1015 			return false;
1016 		}
1017 		//
1018 		if (resultingCollections == null) {
1019 			return true;
1020 		}
1021 		AST ast= ifStatement.getAST();
1022 		ASTRewrite rewrite= ASTRewrite.create(ast);
1023 
1024 		// prepare left and right conditions
1025 		Expression[] newOperands= { null, null };
1026 		breakInfixOperationAtOperation(rewrite, topInfixExpression, andOperator, offset, true, newOperands);
1027 
1028 		Expression leftCondition= newOperands[0];
1029 		Expression rightCondition= newOperands[1];
1030 
1031 		// replace conditions in outer IfStatement
1032 		rewrite.set(ifStatement, IfStatement.EXPRESSION_PROPERTY, leftCondition, null);
1033 
1034 		// prepare inner IfStatement
1035 		IfStatement innerIf= ast.newIfStatement();
1036 
1037 		innerIf.setExpression(rightCondition);
1038 		innerIf.setThenStatement((Statement) rewrite.createMoveTarget(ifStatement.getThenStatement()));
1039 		Block innerBlock= ast.newBlock();
1040 		innerBlock.statements().add(innerIf);
1041 
1042 		Statement elseStatement= ifStatement.getElseStatement();
1043 		if (elseStatement != null) {
1044 			innerIf.setElseStatement((Statement) rewrite.createCopyTarget(elseStatement));
1045 		}
1046 
1047 		// replace outer thenStatement
1048 		rewrite.replace(ifStatement.getThenStatement(), innerBlock, null);
1049 
1050 		// add correction proposal
1051 		String label= CorrectionMessages.AdvancedQuickAssistProcessor_splitAndCondition_description;
1052 		Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
1053 		ASTRewriteCorrectionProposal proposal= new ASTRewriteCorrectionProposal(label, context.getCompilationUnit(), rewrite, IProposalRelevance.SPLIT_AND_CONDITION, image);
1054 		resultingCollections.add(proposal);
1055 		return true;
1056 	}
1057 
isSelectingOperator(ASTNode n1, ASTNode n2, int offset, int length)1058 	private static boolean isSelectingOperator(ASTNode n1, ASTNode n2, int offset, int length) {
1059 		// between the nodes
1060 		if (offset + length <= n2.getStartPosition() && offset >= ASTNodes.getExclusiveEnd(n1)) {
1061 			return true;
1062 		}
1063 		// or exactly select the node (but not with infix expressions)
1064 		if (n1.getStartPosition() == offset && ASTNodes.getExclusiveEnd(n2) == offset + length) {
1065 			if (n1 instanceof InfixExpression || n2 instanceof InfixExpression) {
1066 				return false;
1067 			}
1068 			return true;
1069 		}
1070 		return false;
1071 	}
1072 
isOperatorSelected(InfixExpression infixExpression, int offset, int length)1073 	private static int isOperatorSelected(InfixExpression infixExpression, int offset, int length) {
1074 		ASTNode left= infixExpression.getLeftOperand();
1075 		ASTNode right= infixExpression.getRightOperand();
1076 
1077 		if (isSelectingOperator(left, right, offset, length)) {
1078 			return ASTNodes.getExclusiveEnd(left);
1079 		}
1080 		List<Expression> extended= infixExpression.extendedOperands();
1081 		for (Expression element : extended) {
1082 			left= right;
1083 			right= element;
1084 			if (isSelectingOperator(left, right, offset, length)) {
1085 				return ASTNodes.getExclusiveEnd(left);
1086 			}
1087 		}
1088 		return -1;
1089 	}
1090 
getJoinOrIfStatementsProposals(IInvocationContext context, ASTNode covering, ArrayList<ASTNode> coveredNodes, Collection<ICommandAccess> resultingCollections)1091 	private static boolean getJoinOrIfStatementsProposals(IInvocationContext context, ASTNode covering, ArrayList<ASTNode> coveredNodes, Collection<ICommandAccess> resultingCollections) {
1092 		Operator orOperator= InfixExpression.Operator.CONDITIONAL_OR;
1093 		if (coveredNodes.size() < 2)
1094 			return false;
1095 		// check that all covered nodes are IfStatement's with same 'then' statement and without 'else'
1096 		String commonThenSource= null;
1097 		for (ASTNode node : coveredNodes) {
1098 			if (!(node instanceof IfStatement))
1099 				return false;
1100 			//
1101 			IfStatement ifStatement= (IfStatement) node;
1102 			if (ifStatement.getElseStatement() != null)
1103 				return false;
1104 			//
1105 			Statement thenStatement= ifStatement.getThenStatement();
1106 			try {
1107 				String thenSource= context.getCompilationUnit().getBuffer().getText(thenStatement.getStartPosition(), thenStatement.getLength());
1108 				if (commonThenSource == null) {
1109 					commonThenSource= thenSource;
1110 				} else {
1111 					if (!commonThenSource.equals(thenSource))
1112 						return false;
1113 				}
1114 			} catch (Throwable e) {
1115 				return false;
1116 			}
1117 		}
1118 		if (resultingCollections == null) {
1119 			return true;
1120 		}
1121 		//
1122 		final AST ast= covering.getAST();
1123 		final ASTRewrite rewrite= ASTRewrite.create(ast);
1124 		// prepare OR'ed condition
1125 		InfixExpression condition= null;
1126 		boolean hasRightOperand= false;
1127 		Statement thenStatement= null;
1128 		for (ASTNode astNode : coveredNodes) {
1129 			IfStatement ifStatement= (IfStatement) astNode;
1130 			if (thenStatement == null)
1131 				thenStatement= (Statement) rewrite.createCopyTarget(ifStatement.getThenStatement());
1132 			if (condition == null) {
1133 				condition= ast.newInfixExpression();
1134 				condition.setOperator(orOperator);
1135 				condition.setLeftOperand(getParenthesizedExpressionIfNeeded(ast, rewrite, ifStatement.getExpression(), condition, InfixExpression.LEFT_OPERAND_PROPERTY));
1136 			} else if (!hasRightOperand) {
1137 				condition.setRightOperand(getParenthesizedExpressionIfNeeded(ast, rewrite, ifStatement.getExpression(), condition, InfixExpression.RIGHT_OPERAND_PROPERTY));
1138 				hasRightOperand= true;
1139 			} else {
1140 				InfixExpression newCondition= ast.newInfixExpression();
1141 				newCondition.setOperator(orOperator);
1142 				newCondition.setLeftOperand(condition);
1143 				newCondition.setRightOperand(getParenthesizedExpressionIfNeeded(ast, rewrite, ifStatement.getExpression(), condition, InfixExpression.RIGHT_OPERAND_PROPERTY));
1144 				condition= newCondition;
1145 			}
1146 		}
1147 		// prepare new IfStatement with OR'ed condition
1148 		IfStatement newIf= ast.newIfStatement();
1149 		newIf.setExpression(condition);
1150 		newIf.setThenStatement(thenStatement);
1151 		//
1152 		ListRewrite listRewriter= null;
1153 		for (ASTNode astNode : coveredNodes) {
1154 			IfStatement ifStatement= (IfStatement) astNode;
1155 			if (listRewriter == null) {
1156 				Block sourceBlock= (Block) ifStatement.getParent();
1157 				//int insertIndex = sourceBlock.statements().indexOf(ifStatement);
1158 				listRewriter= rewrite.getListRewrite(sourceBlock, (ChildListPropertyDescriptor) ifStatement.getLocationInParent());
1159 			}
1160 			if (newIf != null) {
1161 				listRewriter.replace(ifStatement, newIf, null);
1162 				newIf= null;
1163 			} else {
1164 				listRewriter.remove(ifStatement, null);
1165 			}
1166 		}
1167 		// add correction proposal
1168 		String label= CorrectionMessages.AdvancedQuickAssistProcessor_joinWithOr_description;
1169 		Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
1170 		ASTRewriteCorrectionProposal proposal= new ASTRewriteCorrectionProposal(label, context.getCompilationUnit(), rewrite, IProposalRelevance.JOIN_IF_STATEMENTS_WITH_OR, image);
1171 		resultingCollections.add(proposal);
1172 		return true;
1173 	}
1174 
getSplitOrConditionProposals(IInvocationContext context, ASTNode node, Collection<ICommandAccess> resultingCollections)1175 	public static boolean getSplitOrConditionProposals(IInvocationContext context, ASTNode node, Collection<ICommandAccess> resultingCollections) {
1176 		Operator orOperator= InfixExpression.Operator.CONDITIONAL_OR;
1177 		// check that user invokes quick assist on infix expression
1178 		if (!(node instanceof InfixExpression)) {
1179 			return false;
1180 		}
1181 		InfixExpression infixExpression= (InfixExpression) node;
1182 		if (infixExpression.getOperator() != orOperator) {
1183 			return false;
1184 		}
1185 		int offset= isOperatorSelected(infixExpression, context.getSelectionOffset(), context.getSelectionLength());
1186 		if (offset == -1) {
1187 			return false;
1188 		}
1189 		// check that infix expression belongs to IfStatement
1190 		Statement statement= ASTResolving.findParentStatement(node);
1191 		if (!(statement instanceof IfStatement)) {
1192 			return false;
1193 		}
1194 		IfStatement ifStatement= (IfStatement) statement;
1195 
1196 		// check that infix expression is part of first level || condition of IfStatement
1197 		InfixExpression topInfixExpression= infixExpression;
1198 		while (topInfixExpression.getParent() instanceof InfixExpression && ((InfixExpression) topInfixExpression.getParent()).getOperator() == orOperator) {
1199 			topInfixExpression= (InfixExpression) topInfixExpression.getParent();
1200 		}
1201 		if (ifStatement.getExpression() != topInfixExpression) {
1202 			return false;
1203 		}
1204 		//
1205 		if (resultingCollections == null) {
1206 			return true;
1207 		}
1208 		AST ast= ifStatement.getAST();
1209 		ASTRewrite rewrite= ASTRewrite.create(ast);
1210 
1211 		// prepare left and right conditions
1212 		Expression[] newOperands= { null, null };
1213 		breakInfixOperationAtOperation(rewrite, topInfixExpression, orOperator, offset, true, newOperands);
1214 
1215 		Expression leftCondition= newOperands[0];
1216 		Expression rightCondition= newOperands[1];
1217 
1218 		// prepare first statement
1219 		rewrite.replace(ifStatement.getExpression(), leftCondition, null);
1220 
1221 		IfStatement secondIf= ast.newIfStatement();
1222 		secondIf.setExpression(rightCondition);
1223 		secondIf.setThenStatement((Statement) rewrite.createCopyTarget(ifStatement.getThenStatement()));
1224 
1225 		Statement elseStatement= ifStatement.getElseStatement();
1226 		if (elseStatement == null) {
1227 			rewrite.set(ifStatement, IfStatement.ELSE_STATEMENT_PROPERTY, secondIf, null);
1228 		} else {
1229 			rewrite.replace(elseStatement, secondIf, null);
1230 			secondIf.setElseStatement((Statement) rewrite.createMoveTarget(elseStatement));
1231 		}
1232 
1233 		// add correction proposal
1234 		String label= CorrectionMessages.AdvancedQuickAssistProcessor_splitOrCondition_description;
1235 		Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
1236 		ASTRewriteCorrectionProposal proposal= new ASTRewriteCorrectionProposal(label, context.getCompilationUnit(), rewrite, IProposalRelevance.SPLIT_OR_CONDITION, image);
1237 		resultingCollections.add(proposal);
1238 		return true;
1239 	}
1240 
getInverseConditionalExpressionProposals(IInvocationContext context, ASTNode covering, Collection<ICommandAccess> resultingCollections)1241 	private static boolean getInverseConditionalExpressionProposals(IInvocationContext context, ASTNode covering, Collection<ICommandAccess> resultingCollections) {
1242 		// try to find conditional expression as parent
1243 		while (covering instanceof Expression) {
1244 			if (covering instanceof ConditionalExpression)
1245 				break;
1246 			covering= covering.getParent();
1247 		}
1248 		if (!(covering instanceof ConditionalExpression)) {
1249 			return false;
1250 		}
1251 		ConditionalExpression expression= (ConditionalExpression) covering;
1252 		//  we could produce quick assist
1253 		if (resultingCollections == null) {
1254 			return true;
1255 		}
1256 		//
1257 		AST ast= covering.getAST();
1258 		ASTRewrite rewrite= ASTRewrite.create(ast);
1259 		// prepare new conditional expression
1260 		ConditionalExpression newExpression= ast.newConditionalExpression();
1261 		newExpression.setExpression(getInversedExpression(rewrite, expression.getExpression()));
1262 		newExpression.setThenExpression((Expression) rewrite.createCopyTarget(expression.getElseExpression()));
1263 		newExpression.setElseExpression((Expression) rewrite.createCopyTarget(expression.getThenExpression()));
1264 		// replace old expression with new
1265 		rewrite.replace(expression, newExpression, null);
1266 		// add correction proposal
1267 		String label= CorrectionMessages.AdvancedQuickAssistProcessor_inverseConditionalExpression_description;
1268 		Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
1269 		ASTRewriteCorrectionProposal proposal= new ASTRewriteCorrectionProposal(label, context.getCompilationUnit(), rewrite, IProposalRelevance.INVERSE_CONDITIONAL_EXPRESSION, image);
1270 		resultingCollections.add(proposal);
1271 		return true;
1272 	}
1273 
getExchangeInnerAndOuterIfConditionsProposals(IInvocationContext context, ASTNode node, Collection<ICommandAccess> resultingCollections)1274 	private static boolean getExchangeInnerAndOuterIfConditionsProposals(IInvocationContext context, ASTNode node, Collection<ICommandAccess> resultingCollections) {
1275 		boolean result= false;
1276 		//
1277 		if (!(node instanceof IfStatement)) {
1278 			return false;
1279 		}
1280 		IfStatement ifStatement= (IfStatement) node;
1281 		if (ifStatement.getElseStatement() != null) {
1282 			return false;
1283 		}
1284 		// case when current IfStatement is sole child of another IfStatement
1285 		{
1286 			IfStatement outerIf= null;
1287 			if (ifStatement.getParent() instanceof IfStatement) {
1288 				outerIf= (IfStatement) ifStatement.getParent();
1289 			} else if (ifStatement.getParent() instanceof Block) {
1290 				Block block= (Block) ifStatement.getParent();
1291 				if (block.getParent() instanceof IfStatement && block.statements().size() == 1) {
1292 					outerIf= (IfStatement) block.getParent();
1293 				}
1294 			}
1295 			if (outerIf != null && outerIf.getElseStatement() == null) {
1296 				if (resultingCollections == null) {
1297 					return true;
1298 				}
1299 				//
1300 				AST ast= node.getAST();
1301 				ASTRewrite rewrite= ASTRewrite.create(ast);
1302 				// prepare conditions
1303 				Expression outerCondition= (Expression) rewrite.createCopyTarget(outerIf.getExpression());
1304 				Expression innerCondition= (Expression) rewrite.createCopyTarget(ifStatement.getExpression());
1305 				// exchange conditions
1306 				rewrite.replace(outerIf.getExpression(), innerCondition, null);
1307 				rewrite.replace(ifStatement.getExpression(), outerCondition, null);
1308 				// add correction proposal
1309 				String label= CorrectionMessages.AdvancedQuickAssistProcessor_exchangeInnerAndOuterIfConditions_description;
1310 				Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
1311 				ASTRewriteCorrectionProposal proposal= new ASTRewriteCorrectionProposal(label, context.getCompilationUnit(), rewrite, IProposalRelevance.EXCHANGE_INNER_AND_OUTER_IF_CONDITIONS, image);
1312 				resultingCollections.add(proposal);
1313 				result= true;
1314 			}
1315 		}
1316 		// case when current IfStatement has another IfStatement as sole child
1317 		{
1318 			IfStatement innerIf= null;
1319 			if (ifStatement.getThenStatement() instanceof IfStatement) {
1320 				innerIf= (IfStatement) ifStatement.getThenStatement();
1321 			} else if (ifStatement.getThenStatement() instanceof Block) {
1322 				Block block= (Block) ifStatement.getThenStatement();
1323 				if (block.statements().size() == 1 && block.statements().get(0) instanceof IfStatement) {
1324 					innerIf= (IfStatement) block.statements().get(0);
1325 				}
1326 			}
1327 			if (innerIf != null && innerIf.getElseStatement() == null) {
1328 				if (resultingCollections == null) {
1329 					return true;
1330 				}
1331 				//
1332 				AST ast= node.getAST();
1333 				ASTRewrite rewrite= ASTRewrite.create(ast);
1334 				// prepare conditions
1335 				Expression innerCondition= (Expression) rewrite.createCopyTarget(innerIf.getExpression());
1336 				Expression outerCondition= (Expression) rewrite.createCopyTarget(ifStatement.getExpression());
1337 				// exchange conditions
1338 				rewrite.replace(innerIf.getExpression(), outerCondition, null);
1339 				rewrite.replace(ifStatement.getExpression(), innerCondition, null);
1340 				// add correction proposal
1341 				String label= CorrectionMessages.AdvancedQuickAssistProcessor_exchangeInnerAndOuterIfConditions_description;
1342 				Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
1343 				ASTRewriteCorrectionProposal proposal= new ASTRewriteCorrectionProposal(label, context.getCompilationUnit(), rewrite, IProposalRelevance.EXCHANGE_INNER_AND_OUTER_IF_CONDITIONS, image);
1344 				resultingCollections.add(proposal);
1345 				result= true;
1346 			}
1347 		}
1348 		return result;
1349 	}
1350 
getExchangeOperandsProposals(IInvocationContext context, ASTNode node, Collection<ICommandAccess> resultingCollections)1351 	private static boolean getExchangeOperandsProposals(IInvocationContext context, ASTNode node, Collection<ICommandAccess> resultingCollections) {
1352 		// check that user invokes quick assist on infix expression
1353 		if (!(node instanceof InfixExpression)) {
1354 			return false;
1355 		}
1356 		InfixExpression infixExpression= (InfixExpression)node;
1357 		Operator operator= infixExpression.getOperator();
1358 		if (operator != InfixExpression.Operator.CONDITIONAL_AND && operator != InfixExpression.Operator.AND
1359 				&& operator != InfixExpression.Operator.CONDITIONAL_OR && operator != InfixExpression.Operator.OR
1360 				&& operator != InfixExpression.Operator.EQUALS && operator != InfixExpression.Operator.NOT_EQUALS
1361 				&& operator != InfixExpression.Operator.LESS && operator != InfixExpression.Operator.LESS_EQUALS
1362 				&& operator != InfixExpression.Operator.GREATER && operator != InfixExpression.Operator.GREATER_EQUALS
1363 				&& operator != InfixExpression.Operator.PLUS && operator != InfixExpression.Operator.TIMES
1364 				&& operator != InfixExpression.Operator.XOR) {
1365 			return false;
1366 		}
1367 
1368 		int offset= isOperatorSelected(infixExpression, context.getSelectionOffset(), context.getSelectionLength());
1369 		if (offset == -1) {
1370 			return false;
1371 		}
1372 
1373 		//  we could produce quick assist
1374 		if (resultingCollections == null) {
1375 			return true;
1376 		}
1377 		AST ast= infixExpression.getAST();
1378 		ASTRewrite rewrite= ASTRewrite.create(ast);
1379 		// prepare left and right expressions
1380 		Expression leftExpression= null;
1381 		Expression rightExpression= null;
1382 		InfixExpression currentExpression= infixExpression;
1383 		leftExpression= combineOperands(rewrite, leftExpression, infixExpression.getLeftOperand(), false, operator);
1384 		if (infixExpression.getRightOperand().getStartPosition() <= context.getSelectionOffset()) {
1385 			leftExpression= combineOperands(rewrite, leftExpression, infixExpression.getRightOperand(), false, operator);
1386 		} else {
1387 			rightExpression= combineOperands(rewrite, rightExpression, infixExpression.getRightOperand(), false, operator);
1388 		}
1389 		for (Iterator<Expression> iter= currentExpression.extendedOperands().iterator(); iter.hasNext();) {
1390 			Expression extendedOperand= iter.next();
1391 			if (extendedOperand.getStartPosition() <= context.getSelectionOffset()) {
1392 				leftExpression= combineOperands(rewrite, leftExpression, extendedOperand, false, operator);
1393 			} else {
1394 				rightExpression= combineOperands(rewrite, rightExpression, extendedOperand, false, operator);
1395 			}
1396 		}
1397 
1398 		if (NecessaryParenthesesChecker.needsParentheses(leftExpression, infixExpression, InfixExpression.RIGHT_OPERAND_PROPERTY)) {
1399 			leftExpression= getParenthesizedExpression(ast, leftExpression);
1400 		}
1401 		if (NecessaryParenthesesChecker.needsParentheses(rightExpression, infixExpression, InfixExpression.LEFT_OPERAND_PROPERTY)) {
1402 			rightExpression= getParenthesizedExpression(ast, rightExpression);
1403 		}
1404 
1405 		if (operator == InfixExpression.Operator.LESS) {
1406 			operator= InfixExpression.Operator.GREATER;
1407 		} else if (operator == InfixExpression.Operator.LESS_EQUALS) {
1408 			operator= InfixExpression.Operator.GREATER_EQUALS;
1409 		} else if (operator == InfixExpression.Operator.GREATER) {
1410 			operator= InfixExpression.Operator.LESS;
1411 		} else if (operator == InfixExpression.Operator.GREATER_EQUALS) {
1412 			operator= InfixExpression.Operator.LESS_EQUALS;
1413 		}
1414 
1415 		// create new infix expression
1416 		InfixExpression newInfix= ast.newInfixExpression();
1417 		newInfix.setOperator(operator);
1418 		newInfix.setLeftOperand(rightExpression);
1419 		newInfix.setRightOperand(leftExpression);
1420 		rewrite.replace(infixExpression, newInfix, null);
1421 		// add correction proposal
1422 		String label= CorrectionMessages.AdvancedQuickAssistProcessor_exchangeOperands_description;
1423 		Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
1424 		ASTRewriteCorrectionProposal proposal= new ASTRewriteCorrectionProposal(label, context.getCompilationUnit(), rewrite, IProposalRelevance.EXCHANGE_OPERANDS, image);
1425 		resultingCollections.add(proposal);
1426 		return true;
1427 	}
1428 
1429 	/*
1430 	 * Breaks an infix operation with possible extended operators at the given operator and returns the new left and right operands.
1431 	 * a & b & c   ->  [[a' & b' ] & c' ]   (c' == copy of c)
1432 	 */
breakInfixOperationAtOperation(ASTRewrite rewrite, Expression expression, Operator operator, int operatorOffset, boolean removeParentheses, Expression[] res)1433 	private static void breakInfixOperationAtOperation(ASTRewrite rewrite, Expression expression, Operator operator, int operatorOffset, boolean removeParentheses, Expression[] res) {
1434 		if (expression.getStartPosition() + expression.getLength() <= operatorOffset) {
1435 			// add to the left
1436 			res[0]= combineOperands(rewrite, res[0], expression, removeParentheses, operator);
1437 			return;
1438 		}
1439 		if (operatorOffset <= expression.getStartPosition()) {
1440 			// add to the right
1441 			res[1]= combineOperands(rewrite, res[1], expression, removeParentheses, operator);
1442 			return;
1443 		}
1444 		if (!(expression instanceof InfixExpression)) {
1445 			throw new IllegalArgumentException("Cannot break up non-infix expression"); //$NON-NLS-1$
1446 		}
1447 		InfixExpression infixExpression= (InfixExpression) expression;
1448 		if (infixExpression.getOperator() != operator) {
1449 			throw new IllegalArgumentException("Incompatible operator"); //$NON-NLS-1$
1450 		}
1451 		breakInfixOperationAtOperation(rewrite, infixExpression.getLeftOperand(), operator, operatorOffset, removeParentheses, res);
1452 		breakInfixOperationAtOperation(rewrite, infixExpression.getRightOperand(), operator, operatorOffset, removeParentheses, res);
1453 
1454 		List<Expression> extended= infixExpression.extendedOperands();
1455 		for (Expression element : extended) {
1456 			breakInfixOperationAtOperation(rewrite, element, operator, operatorOffset, removeParentheses, res);
1457 		}
1458 	}
1459 
combineOperands(ASTRewrite rewrite, Expression existing, Expression originalNode, boolean removeParentheses, Operator operator)1460 	private static Expression combineOperands(ASTRewrite rewrite, Expression existing, Expression originalNode, boolean removeParentheses, Operator operator) {
1461 		if (existing == null && removeParentheses) {
1462 			originalNode= ASTNodes.getUnparenthesedExpression(originalNode);
1463 		}
1464 		Expression newRight= (Expression)rewrite.createMoveTarget(originalNode);
1465 		if (originalNode instanceof InfixExpression) {
1466 			((InfixExpression)newRight).setOperator(((InfixExpression)originalNode).getOperator());
1467 		}
1468 
1469 		if (existing == null) {
1470 			return newRight;
1471 		}
1472 		AST ast= rewrite.getAST();
1473 		InfixExpression infix= ast.newInfixExpression();
1474 		infix.setOperator(operator);
1475 		infix.setLeftOperand(existing);
1476 		infix.setRightOperand(newRight);
1477 		return infix;
1478 	}
1479 
getCastAndAssignIfStatementProposals(IInvocationContext context, ASTNode node, Collection<ICommandAccess> resultingCollections)1480 	private static boolean getCastAndAssignIfStatementProposals(IInvocationContext context, ASTNode node, Collection<ICommandAccess> resultingCollections) {
1481 		if (node instanceof IfStatement) {
1482 			node= ((IfStatement)node).getExpression();
1483 		} else if (node instanceof WhileStatement) {
1484 			node= ((WhileStatement)node).getExpression();
1485 		} else if (node instanceof Block) {
1486 			List<Statement> statements= ((Block)node).statements();
1487 			if (statements.size() > 0) {
1488 				if (context.getSelectionOffset() > statements.get(0).getStartPosition()) {
1489 					return false;
1490 				}
1491 			}
1492 			ASTNode parent= node.getParent();
1493 			Expression expression= null;
1494 			if (parent instanceof IfStatement) {
1495 				expression= ((IfStatement)parent).getExpression();
1496 			} else if (parent instanceof WhileStatement) {
1497 				expression= ((WhileStatement)parent).getExpression();
1498 			} else {
1499 				return false;
1500 			}
1501 
1502 			if (expression instanceof InstanceofExpression) {
1503 				node= expression;
1504 			} else {
1505 				final ArrayList<InstanceofExpression> nodes= new ArrayList<>();
1506 				expression.accept(new ASTVisitor() {
1507 					@Override
1508 					public boolean visit(InstanceofExpression instanceofExpression) {
1509 						nodes.add(instanceofExpression);
1510 						return false;
1511 					}
1512 				});
1513 
1514 				if (nodes.size() != 1) {
1515 					return false;
1516 				}
1517 				node= nodes.get(0);
1518 			}
1519 		} else {
1520 			while (node != null && !(node instanceof InstanceofExpression) && !(node instanceof Statement)) {
1521 				node= node.getParent();
1522 			}
1523 		}
1524 
1525 		if (!(node instanceof InstanceofExpression)) {
1526 			return false;
1527 		}
1528 		InstanceofExpression expression= (InstanceofExpression)node;
1529 		// test that we are the expression of a 'while' or 'if'
1530 		while (node.getParent() instanceof Expression) {
1531 			node= node.getParent();
1532 		}
1533 		StructuralPropertyDescriptor locationInParent= node.getLocationInParent();
1534 
1535 		boolean negated= isNegated(expression);
1536 
1537 		Statement body= null;
1538 		ASTNode insertionPosition= null;
1539 		if (negated) {
1540 			insertionPosition= node.getParent();
1541 			if (locationInParent == IfStatement.EXPRESSION_PROPERTY) {
1542 				body= ((IfStatement)node.getParent()).getElseStatement();
1543 				if (body != null) {
1544 					negated= false;
1545 				}
1546 			}
1547 			if (body == null && insertionPosition.getParent() instanceof Block) {
1548 				body= (Statement)insertionPosition.getParent();
1549 			}
1550 		} else {
1551 			if (locationInParent == IfStatement.EXPRESSION_PROPERTY) {
1552 				body= ((IfStatement)node.getParent()).getThenStatement();
1553 			} else if (locationInParent == WhileStatement.EXPRESSION_PROPERTY) {
1554 				body= ((WhileStatement)node.getParent()).getBody();
1555 			}
1556 		}
1557 		if (body == null) {
1558 			return false;
1559 		}
1560 
1561 		Type originalType= expression.getRightOperand();
1562 		if (originalType.resolveBinding() == null) {
1563 			return false;
1564 		}
1565 
1566 		//  we could produce quick assist
1567 		if (resultingCollections == null) {
1568 			return true;
1569 		}
1570 
1571 		final String KEY_NAME= "name"; //$NON-NLS-1$
1572 		final String KEY_TYPE= "type"; //$NON-NLS-1$
1573 		//
1574 		AST ast= expression.getAST();
1575 		ASTRewrite rewrite= ASTRewrite.create(ast);
1576 		ICompilationUnit cu= context.getCompilationUnit();
1577 		// prepare correction proposal
1578 		String label= CorrectionMessages.AdvancedQuickAssistProcessor_castAndAssign;
1579 		Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_LOCAL);
1580 		LinkedCorrectionProposal proposal= new LinkedCorrectionProposal(label, cu, rewrite, IProposalRelevance.CAST_AND_ASSIGN, image);
1581 		// prepare possible variable names
1582 		List<String> excludedNames= Arrays.asList(ASTResolving.getUsedVariableNames(body));
1583 		String[] varNames= suggestLocalVariableNames(cu, originalType.resolveBinding(), excludedNames);
1584 		for (String varName : varNames) {
1585 			proposal.addLinkedPositionProposal(KEY_NAME, varName, null);
1586 		}
1587 		CastExpression castExpression= ast.newCastExpression();
1588 		castExpression.setExpression((Expression)rewrite.createCopyTarget(expression.getLeftOperand()));
1589 		castExpression.setType((Type)ASTNode.copySubtree(ast, originalType));
1590 		// prepare new variable declaration
1591 		VariableDeclarationFragment vdf= ast.newVariableDeclarationFragment();
1592 		vdf.setName(ast.newSimpleName(varNames[0]));
1593 		vdf.setInitializer(castExpression);
1594 		// prepare new variable declaration statement
1595 		VariableDeclarationStatement vds= ast.newVariableDeclarationStatement(vdf);
1596 		vds.setType((Type)ASTNode.copySubtree(ast, originalType));
1597 
1598 		// add new variable declaration statement
1599 		if (negated) {
1600 			ListRewrite listRewriter= rewrite.getListRewrite(body, Block.STATEMENTS_PROPERTY);
1601 			listRewriter.insertAfter(vds, insertionPosition, null);
1602 		} else {
1603 			if (body instanceof Block) {
1604 				ListRewrite listRewriter= rewrite.getListRewrite(body, Block.STATEMENTS_PROPERTY);
1605 				listRewriter.insertAt(vds, 0, null);
1606 			} else {
1607 				Block newBlock= ast.newBlock();
1608 				List<Statement> statements= newBlock.statements();
1609 				statements.add(vds);
1610 				statements.add((Statement) rewrite.createMoveTarget(body));
1611 				rewrite.replace(body, newBlock, null);
1612 			}
1613 		}
1614 
1615 		// setup linked positions
1616 		proposal.addLinkedPosition(rewrite.track(vdf.getName()), true, KEY_NAME);
1617 		proposal.addLinkedPosition(rewrite.track(vds.getType()), false, KEY_TYPE);
1618 		proposal.addLinkedPosition(rewrite.track(castExpression.getType()), false, KEY_TYPE);
1619 		proposal.setEndPosition(rewrite.track(vds)); // set cursor after expression statement
1620 		// add correction proposal
1621 		resultingCollections.add(proposal);
1622 		return true;
1623 	}
1624 
isNegated(Expression expression)1625 	private static boolean isNegated(Expression expression) {
1626 		if (!(expression.getParent() instanceof ParenthesizedExpression))
1627 			return false;
1628 
1629 		ParenthesizedExpression parenthesis= (ParenthesizedExpression) expression.getParent();
1630 		if (!(parenthesis.getParent() instanceof PrefixExpression))
1631 			return false;
1632 
1633 		PrefixExpression prefix= (PrefixExpression) parenthesis.getParent();
1634 		if (prefix.getOperator() != PrefixExpression.Operator.NOT)
1635 			return false;
1636 
1637 		return true;
1638 	}
1639 
suggestLocalVariableNames(ICompilationUnit cu, ITypeBinding binding, List<String> excluded)1640 	private static String[] suggestLocalVariableNames(ICompilationUnit cu, ITypeBinding binding, List<String> excluded) {
1641 		return StubUtility.getVariableNameSuggestions(NamingConventions.VK_LOCAL, cu.getJavaProject(), binding, null, excluded);
1642 	}
1643 
getCombineStringProposals(IInvocationContext context, ASTNode node, Collection<ICommandAccess> resultingCollections)1644 	private static boolean getCombineStringProposals(IInvocationContext context, ASTNode node, Collection<ICommandAccess> resultingCollections) {
1645 		// we work with InfixExpressions
1646 		InfixExpression infixExpression;
1647 		if (node instanceof InfixExpression) {
1648 			infixExpression= (InfixExpression) node;
1649 		} else if (node.getParent() instanceof InfixExpression) {
1650 			infixExpression= (InfixExpression) node.getParent();
1651 		} else {
1652 			return false;
1653 		}
1654 
1655 
1656 		// only + is valid for combining strings
1657 		if (!infixExpression.getOperator().equals(InfixExpression.Operator.PLUS)) {
1658 			return false;
1659 		}
1660 
1661 		// all expressions must be strings
1662 		Expression leftOperand= infixExpression.getLeftOperand();
1663 		Expression rightOperand= infixExpression.getRightOperand();
1664 		if (!(leftOperand instanceof StringLiteral) || !(rightOperand instanceof StringLiteral)) {
1665 			return false;
1666 		}
1667 
1668 		StringLiteral leftString= (StringLiteral) leftOperand;
1669 		StringLiteral rightString= (StringLiteral) rightOperand;
1670 
1671 		if (resultingCollections == null) {
1672 			return true;
1673 		}
1674 
1675 		// begin building combined string
1676 		StringBuilder stringBuilder= new StringBuilder(leftString.getLiteralValue());
1677 		stringBuilder.append(rightString.getLiteralValue());
1678 
1679 		// append extended string literals
1680 		for (Object operand : infixExpression.extendedOperands()) {
1681 			if (!(operand instanceof StringLiteral))
1682 				return false;
1683 			StringLiteral stringLiteral= (StringLiteral) operand;
1684 			stringBuilder.append(stringLiteral.getLiteralValue());
1685 		}
1686 
1687 		// prepare new string literal
1688 		AST ast= node.getAST();
1689 		StringLiteral combinedStringLiteral= ast.newStringLiteral();
1690 		combinedStringLiteral.setLiteralValue(stringBuilder.toString());
1691 
1692 		ASTRewrite rewrite= ASTRewrite.create(ast);
1693 		rewrite.replace(infixExpression, combinedStringLiteral, null);
1694 
1695 		// add correction proposal
1696 		String label= CorrectionMessages.AdvancedQuickAssistProcessor_combineSelectedStrings;
1697 		Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
1698 		LinkedCorrectionProposal proposal= new LinkedCorrectionProposal(label, context.getCompilationUnit(), rewrite, IProposalRelevance.COMBINE_STRINGS, image);
1699 		resultingCollections.add(proposal);
1700 		return true;
1701 	}
1702 
getPickOutStringProposals(IInvocationContext context, ASTNode node, Collection<ICommandAccess> resultingCollections)1703 	private static boolean getPickOutStringProposals(IInvocationContext context, ASTNode node, Collection<ICommandAccess> resultingCollections) {
1704 		// we work with String's
1705 		if (!(node instanceof StringLiteral)) {
1706 			return false;
1707 		}
1708 		// user should select part of String
1709 		int selectionPos= context.getSelectionOffset();
1710 		int selectionLen= context.getSelectionLength();
1711 		if (selectionLen == 0) {
1712 			return false;
1713 		}
1714 		int valueStart= node.getStartPosition() + 1;
1715 		int valueEnd= node.getStartPosition() + node.getLength() - 1;
1716 
1717 		// selection must be inside node and the quotes and not contain the full value
1718 		if (selectionPos < valueStart || selectionPos + selectionLen > valueEnd || valueEnd - valueStart == selectionLen) {
1719 			return false;
1720 		}
1721 
1722 		// prepare string parts positions
1723 		StringLiteral stringLiteral= (StringLiteral) node;
1724 		String stringValue= stringLiteral.getEscapedValue();
1725 
1726 		int firstPos= selectionPos - node.getStartPosition();
1727 		int secondPos= firstPos + selectionLen;
1728 
1729 
1730 		// prepare new string literals
1731 
1732 		AST ast= node.getAST();
1733 		StringLiteral leftLiteral= ast.newStringLiteral();
1734 		StringLiteral centerLiteral= ast.newStringLiteral();
1735 		StringLiteral rightLiteral= ast.newStringLiteral();
1736 		try {
1737 			leftLiteral.setEscapedValue('"' + stringValue.substring(1, firstPos) + '"');
1738 			centerLiteral.setEscapedValue('"' + stringValue.substring(firstPos, secondPos) + '"');
1739 			rightLiteral.setEscapedValue('"' + stringValue.substring(secondPos, stringValue.length() - 1) + '"');
1740 		} catch (IllegalArgumentException e) {
1741 			return false;
1742 		}
1743 		if (resultingCollections == null) {
1744 			return true;
1745 		}
1746 
1747 		ASTRewrite rewrite= ASTRewrite.create(ast);
1748 
1749 		// prepare new expression instead of StringLiteral
1750 		InfixExpression expression= ast.newInfixExpression();
1751 		expression.setOperator(InfixExpression.Operator.PLUS);
1752 		if (firstPos != 1) {
1753 			expression.setLeftOperand(leftLiteral);
1754 		}
1755 
1756 
1757 		if (firstPos == 1) {
1758 			expression.setLeftOperand(centerLiteral);
1759 		} else {
1760 			expression.setRightOperand(centerLiteral);
1761 		}
1762 
1763 		if (secondPos < stringValue.length() - 1) {
1764 			if (firstPos == 1) {
1765 				expression.setRightOperand(rightLiteral);
1766 			} else {
1767 				expression.extendedOperands().add(rightLiteral);
1768 			}
1769 		}
1770 		// use new expression instead of old StirngLiteral
1771 		rewrite.replace(stringLiteral, expression, null);
1772 		// add correction proposal
1773 		String label= CorrectionMessages.AdvancedQuickAssistProcessor_pickSelectedString;
1774 		Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
1775 		LinkedCorrectionProposal proposal= new LinkedCorrectionProposal(label, context.getCompilationUnit(), rewrite, IProposalRelevance.PICK_SELECTED_STRING, image);
1776 		proposal.addLinkedPosition(rewrite.track(centerLiteral), true, "CENTER_STRING"); //$NON-NLS-1$
1777 		resultingCollections.add(proposal);
1778 		return true;
1779 	}
1780 
getSingleStatement(Statement statement)1781 	private static Statement getSingleStatement(Statement statement) {
1782 		if (statement instanceof Block) {
1783 			List<Statement> blockStatements= ((Block) statement).statements();
1784 			if (blockStatements.size() != 1) {
1785 				return null;
1786 			}
1787 			return blockStatements.get(0);
1788 		}
1789 		return statement;
1790 	}
1791 
getReplaceIfElseWithConditionalProposals(IInvocationContext context, ASTNode node, Collection<ICommandAccess> resultingCollections)1792 	private static boolean getReplaceIfElseWithConditionalProposals(IInvocationContext context, ASTNode node, Collection<ICommandAccess> resultingCollections) {
1793 		if (!(node instanceof IfStatement)) {
1794 			return false;
1795 		}
1796 		IfStatement ifStatement= (IfStatement) node;
1797 		Statement thenStatement= getSingleStatement(ifStatement.getThenStatement());
1798 		Statement elseStatement= getSingleStatement(ifStatement.getElseStatement());
1799 		if (thenStatement == null || elseStatement == null) {
1800 			return false;
1801 		}
1802 		Expression assigned= null;
1803 		Expression thenExpression= null;
1804 		Expression elseExpression= null;
1805 
1806 		ITypeBinding exprBinding= null;
1807 		if (thenStatement instanceof ReturnStatement && elseStatement instanceof ReturnStatement) {
1808 			thenExpression= ((ReturnStatement) thenStatement).getExpression();
1809 			elseExpression= ((ReturnStatement) elseStatement).getExpression();
1810 			MethodDeclaration declaration= ASTResolving.findParentMethodDeclaration(node);
1811 			if (declaration == null || declaration.isConstructor()) {
1812 				return false;
1813 			}
1814 			exprBinding= declaration.getReturnType2().resolveBinding();
1815 		} else if (thenStatement instanceof ExpressionStatement && elseStatement instanceof ExpressionStatement) {
1816 			Expression inner1= ((ExpressionStatement) thenStatement).getExpression();
1817 			Expression inner2= ((ExpressionStatement) elseStatement).getExpression();
1818 			if (inner1 instanceof Assignment && inner2 instanceof Assignment) {
1819 				Assignment assign1= (Assignment) inner1;
1820 				Assignment assign2= (Assignment) inner2;
1821 				Expression left1= assign1.getLeftHandSide();
1822 				Expression left2= assign2.getLeftHandSide();
1823 				if (left1 instanceof Name && left2 instanceof Name && assign1.getOperator() == assign2.getOperator()) {
1824 					IBinding bind1= ((Name) left1).resolveBinding();
1825 					IBinding bind2= ((Name) left2).resolveBinding();
1826 					if (bind1 == bind2 && bind1 instanceof IVariableBinding) {
1827 						assigned= left1;
1828 						exprBinding= ((IVariableBinding) bind1).getType();
1829 						thenExpression= assign1.getRightHandSide();
1830 						elseExpression= assign2.getRightHandSide();
1831 					}
1832 				}
1833 			}
1834 		}
1835 		if (thenExpression == null || elseExpression == null) {
1836 			return false;
1837 		}
1838 
1839 		//  we could produce quick assist
1840 		if (resultingCollections == null) {
1841 			return true;
1842 		}
1843 		//
1844 		AST ast= node.getAST();
1845 		ASTRewrite rewrite= ASTRewrite.create(ast);
1846 		TightSourceRangeComputer sourceRangeComputer= new TightSourceRangeComputer();
1847 		sourceRangeComputer.addTightSourceNode(ifStatement);
1848 		rewrite.setTargetSourceRangeComputer(sourceRangeComputer);
1849 
1850 		String label= CorrectionMessages.AdvancedQuickAssistProcessor_replaceIfWithConditional;
1851 		Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
1852 		ASTRewriteCorrectionProposal proposal= new ASTRewriteCorrectionProposal(label, context.getCompilationUnit(), rewrite, IProposalRelevance.REPLACE_IF_ELSE_WITH_CONDITIONAL, image);
1853 
1854 
1855 		// prepare conditional expression
1856 		ConditionalExpression conditionalExpression= ast.newConditionalExpression();
1857 		Expression conditionCopy= (Expression) rewrite.createCopyTarget(ifStatement.getExpression());
1858 		conditionalExpression.setExpression(conditionCopy);
1859 		Expression thenCopy= (Expression) rewrite.createCopyTarget(thenExpression);
1860 		Expression elseCopy= (Expression) rewrite.createCopyTarget(elseExpression);
1861 
1862 
1863 		IJavaProject project= context.getCompilationUnit().getJavaProject();
1864 		if (!JavaModelUtil.is50OrHigher(project)) {
1865 			ITypeBinding thenBinding= thenExpression.resolveTypeBinding();
1866 			ITypeBinding elseBinding= elseExpression.resolveTypeBinding();
1867 			if (thenBinding != null && elseBinding != null && exprBinding != null && !elseBinding.isAssignmentCompatible(thenBinding)) {
1868 				CastExpression castException= ast.newCastExpression();
1869 				ImportRewrite importRewrite= proposal.createImportRewrite(context.getASTRoot());
1870 				ImportRewriteContext importRewriteContext= new ContextSensitiveImportRewriteContext(node, importRewrite);
1871 				castException.setType(importRewrite.addImport(exprBinding, ast, importRewriteContext, TypeLocation.CAST));
1872 				castException.setExpression(elseCopy);
1873 				elseCopy= castException;
1874 			}
1875 		} else if (JavaModelUtil.is17OrHigher(project)) {
1876 			addExplicitTypeArgumentsIfNecessary(rewrite, proposal, thenExpression);
1877 			addExplicitTypeArgumentsIfNecessary(rewrite, proposal, elseExpression);
1878 		}
1879 		conditionalExpression.setThenExpression(thenCopy);
1880 		conditionalExpression.setElseExpression(elseCopy);
1881 
1882 		// replace 'if' statement with conditional expression
1883 		if (assigned == null) {
1884 			ReturnStatement returnStatement= ast.newReturnStatement();
1885 			returnStatement.setExpression(conditionalExpression);
1886 			rewrite.replace(ifStatement, returnStatement, null);
1887 		} else {
1888 			Assignment assignment= ast.newAssignment();
1889 			assignment.setLeftHandSide((Expression) rewrite.createCopyTarget(assigned));
1890 			assignment.setRightHandSide(conditionalExpression);
1891 			assignment.setOperator(((Assignment) assigned.getParent()).getOperator());
1892 
1893 			ExpressionStatement expressionStatement= ast.newExpressionStatement(assignment);
1894 			rewrite.replace(ifStatement, expressionStatement, null);
1895 		}
1896 
1897 		// add correction proposal
1898 		resultingCollections.add(proposal);
1899 		return true;
1900 	}
1901 
addExplicitTypeArgumentsIfNecessary(ASTRewrite rewrite, ASTRewriteCorrectionProposal proposal, Expression invocation)1902 	private static void addExplicitTypeArgumentsIfNecessary(ASTRewrite rewrite, ASTRewriteCorrectionProposal proposal, Expression invocation) {
1903 		if (Invocations.isResolvedTypeInferredFromExpectedType(invocation)) {
1904 			ITypeBinding[] typeArguments= Invocations.getInferredTypeArguments(invocation);
1905 			if (typeArguments == null)
1906 				return;
1907 
1908 			ImportRewrite importRewrite= proposal.getImportRewrite();
1909 			if (importRewrite == null) {
1910 				importRewrite= proposal.createImportRewrite((CompilationUnit) invocation.getRoot());
1911 			}
1912 			ImportRewriteContext importRewriteContext= new ContextSensitiveImportRewriteContext(invocation, importRewrite);
1913 
1914 			AST ast= invocation.getAST();
1915 			ListRewrite typeArgsRewrite= Invocations.getInferredTypeArgumentsRewrite(rewrite, invocation);
1916 
1917 			for (ITypeBinding typeArgument : typeArguments) {
1918 				Type typeArgumentNode= importRewrite.addImport(typeArgument, ast, importRewriteContext, TypeLocation.TYPE_ARGUMENT);
1919 				typeArgsRewrite.insertLast(typeArgumentNode, null);
1920 			}
1921 
1922 			if (invocation instanceof MethodInvocation) {
1923 				MethodInvocation methodInvocation= (MethodInvocation) invocation;
1924 				Expression expression= methodInvocation.getExpression();
1925 				if (expression == null) {
1926 					IMethodBinding methodBinding= methodInvocation.resolveMethodBinding();
1927 					if (methodBinding != null && Modifier.isStatic(methodBinding.getModifiers())) {
1928 						expression= ast.newName(importRewrite.addImport(methodBinding.getDeclaringClass().getTypeDeclaration(), importRewriteContext));
1929 					} else {
1930 						expression= ast.newThisExpression();
1931 					}
1932 					rewrite.set(invocation, MethodInvocation.EXPRESSION_PROPERTY, expression, null);
1933 				}
1934 			}
1935 		}
1936 	}
1937 
createReturnExpression(ASTRewrite rewrite, Expression expression)1938 	private static ReturnStatement createReturnExpression(ASTRewrite rewrite, Expression expression) {
1939 		AST ast= rewrite.getAST();
1940 		ReturnStatement thenReturn= ast.newReturnStatement();
1941 		Expression cleanExpression= expression;
1942 		cleanExpression= ASTNodes.getUnparenthesedExpression(cleanExpression);
1943 		thenReturn.setExpression((Expression) rewrite.createCopyTarget(cleanExpression));
1944 		return thenReturn;
1945 	}
1946 
createAssignmentStatement(ASTRewrite rewrite, Assignment.Operator assignmentOperator, Expression origAssignee, Expression origAssigned)1947 	private static Statement createAssignmentStatement(ASTRewrite rewrite, Assignment.Operator assignmentOperator, Expression origAssignee, Expression origAssigned) {
1948 		AST ast= rewrite.getAST();
1949 		Assignment elseAssignment= ast.newAssignment();
1950 		elseAssignment.setOperator(assignmentOperator);
1951 		Expression left= ASTNodes.getUnparenthesedExpression(origAssignee);
1952 		Expression right= ASTNodes.getUnparenthesedExpression(origAssigned);
1953 		elseAssignment.setLeftHandSide((Expression) rewrite.createCopyTarget(left));
1954 		elseAssignment.setRightHandSide((Expression) rewrite.createCopyTarget(right));
1955 		ExpressionStatement statement= ast.newExpressionStatement(elseAssignment);
1956 		return statement;
1957 	}
1958 
getReplaceConditionalWithIfElseProposals(IInvocationContext context, ASTNode covering, Collection<ICommandAccess> resultingCollections)1959 	private static boolean getReplaceConditionalWithIfElseProposals(IInvocationContext context, ASTNode covering, Collection<ICommandAccess> resultingCollections) {
1960 		ASTNode node= covering;
1961 		while (!(node instanceof ConditionalExpression) && node instanceof Expression) {
1962 			node= node.getParent();
1963 		}
1964 		if (!(node instanceof ConditionalExpression)) {
1965 			node= covering;
1966 			while (node != null && !(node instanceof Statement)) {
1967 				node= node.getParent();
1968 			}
1969 			if (node instanceof VariableDeclarationStatement) {
1970 				node= (ASTNode)(((VariableDeclarationStatement)node).fragments().get(0));
1971 				node= ((VariableDeclarationFragment)node).getInitializer();
1972 			}
1973 			if (node instanceof ExpressionStatement) {
1974 				node= ((ExpressionStatement)node).getExpression();
1975 				if (node instanceof Assignment) {
1976 					node= ((Assignment)node).getRightHandSide();
1977 				}
1978 			}
1979 			if (node instanceof ReturnStatement) {
1980 				node= ((ReturnStatement)node).getExpression();
1981 			}
1982 		}
1983 
1984 		if (!(node instanceof ConditionalExpression)) {
1985 			return false;
1986 		}
1987 
1988 		ASTNode parentNodeNoParenthesis= node;
1989 		StructuralPropertyDescriptor locationInParent= node.getLocationInParent();
1990 		while(locationInParent.getNodeClass() == ParenthesizedExpression.class) {
1991 			parentNodeNoParenthesis= parentNodeNoParenthesis.getParent();
1992 			locationInParent= parentNodeNoParenthesis.getLocationInParent();
1993 		}
1994 		if (locationInParent == Assignment.RIGHT_HAND_SIDE_PROPERTY) {
1995 			if (parentNodeNoParenthesis.getParent().getLocationInParent() != ExpressionStatement.EXPRESSION_PROPERTY) {
1996 				return false;
1997 			}
1998 		} else if (locationInParent == VariableDeclarationFragment.INITIALIZER_PROPERTY) {
1999 			ASTNode statement= parentNodeNoParenthesis.getParent().getParent();
2000 			if (!(statement instanceof VariableDeclarationStatement) || statement.getLocationInParent() != Block.STATEMENTS_PROPERTY) {
2001 				return false;
2002 			}
2003 		} else if (locationInParent != ReturnStatement.EXPRESSION_PROPERTY) {
2004 			return false;
2005 		}
2006 
2007 		ConditionalExpression conditional= (ConditionalExpression) node;
2008 		//  we could produce quick assist
2009 		if (resultingCollections == null) {
2010 			return true;
2011 		}
2012 
2013 		AST ast= node.getAST();
2014 		ASTRewrite rewrite= ASTRewrite.create(ast);
2015 		// prepare new 'if' statement
2016 		Expression expression= ASTNodes.getUnparenthesedExpression(conditional.getExpression());
2017 		IfStatement ifStatement= ast.newIfStatement();
2018 		ifStatement.setExpression((Expression) rewrite.createCopyTarget(expression));
2019 
2020 		if (locationInParent == Assignment.RIGHT_HAND_SIDE_PROPERTY) {
2021 			ASTNode replaceNode= node;
2022 			while(!(replaceNode instanceof Assignment) && replaceNode != null) {
2023 				replaceNode= replaceNode.getParent();
2024 			}
2025 			Assignment assignment= (Assignment) replaceNode;
2026 			Expression assignee= assignment.getLeftHandSide();
2027 			Assignment.Operator op= assignment.getOperator();
2028 			ifStatement.setThenStatement(createAssignmentStatement(rewrite, op, assignee, conditional.getThenExpression()));
2029 			ifStatement.setElseStatement(createAssignmentStatement(rewrite, op, assignee, conditional.getElseExpression()));
2030 			rewrite.replace(replaceNode.getParent(), ifStatement, null);
2031 
2032 		} else if (locationInParent == ReturnStatement.EXPRESSION_PROPERTY) {
2033 			ASTNode replaceNode= node;
2034 			while(!(replaceNode instanceof ReturnStatement) && replaceNode != null) {
2035 				replaceNode= replaceNode.getParent();
2036 			}
2037 			ifStatement.setThenStatement(createReturnExpression(rewrite, conditional.getThenExpression()));
2038 			ifStatement.setElseStatement(createReturnExpression(rewrite, conditional.getElseExpression()));
2039 			// replace return conditional expression with if/then/else/return
2040 			rewrite.replace(replaceNode, ifStatement, null);
2041 
2042 		} else if (locationInParent == VariableDeclarationFragment.INITIALIZER_PROPERTY) {
2043 			ASTNode replaceNode= node;
2044 			while(!(replaceNode instanceof VariableDeclarationFragment) && replaceNode != null) {
2045 				replaceNode= replaceNode.getParent();
2046 			}
2047 			VariableDeclarationFragment frag= (VariableDeclarationFragment) replaceNode;
2048 			Assignment.Operator op= Assignment.Operator.ASSIGN;
2049 			Expression assignee= frag.getName();
2050 			ifStatement.setThenStatement(createAssignmentStatement(rewrite, op, assignee, conditional.getThenExpression()));
2051 			ifStatement.setElseStatement(createAssignmentStatement(rewrite, op, assignee, conditional.getElseExpression()));
2052 			rewrite.set(frag, VariableDeclarationFragment.INITIALIZER_PROPERTY, null, null); // clear initializer
2053 			ASTNode statement= frag.getParent();
2054 			rewrite.getListRewrite(statement.getParent(), Block.STATEMENTS_PROPERTY).insertAfter(ifStatement, statement, null);
2055 		}
2056 
2057 		// add correction proposal
2058 		String label= CorrectionMessages.AdvancedQuickAssistProcessor_replaceConditionalWithIf;
2059 		Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
2060 		ASTRewriteCorrectionProposal proposal= new ASTRewriteCorrectionProposal(label, context.getCompilationUnit(), rewrite, IProposalRelevance.REPLACE_CONDITIONAL_WITH_IF_ELSE, image);
2061 		resultingCollections.add(proposal);
2062 		return true;
2063 	}
2064 
getInverseLocalVariableProposals(IInvocationContext context, ASTNode covering, Collection<ICommandAccess> resultingCollections)2065 	private static boolean getInverseLocalVariableProposals(IInvocationContext context, ASTNode covering, Collection<ICommandAccess> resultingCollections) {
2066 		final AST ast= covering.getAST();
2067 		// cursor should be placed on variable name
2068 		if (!(covering instanceof SimpleName)) {
2069 			return false;
2070 		}
2071 		SimpleName coveringName= (SimpleName) covering;
2072 		if (!coveringName.isDeclaration()) {
2073 			return false;
2074 		}
2075 		// prepare bindings
2076 		final IBinding variableBinding= coveringName.resolveBinding();
2077 		if (!(variableBinding instanceof IVariableBinding)) {
2078 			return false;
2079 		}
2080 		IVariableBinding binding= (IVariableBinding) variableBinding;
2081 		if (binding.isField()) {
2082 			return false;
2083 		}
2084 		// we operate only on boolean variable
2085 		if (!isBoolean(coveringName)) {
2086 			return false;
2087 		}
2088 		//  we could produce quick assist
2089 		if (resultingCollections == null) {
2090 			return true;
2091 		}
2092 		// find linked nodes
2093 		final BodyDeclaration bodyDecl= ASTResolving.findParentBodyDeclaration(covering);
2094 		if (bodyDecl == null) {
2095 			return false;
2096 		}
2097 		SimpleName[] linkedNodes= LinkedNodeFinder.findByBinding(bodyDecl, variableBinding);
2098 		//
2099 		final ASTRewrite rewrite= ASTRewrite.create(ast);
2100 		// create proposal
2101 		String label= CorrectionMessages.AdvancedQuickAssistProcessor_inverseBooleanVariable;
2102 		Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
2103 		final String KEY_NAME= "name"; //$NON-NLS-1$
2104 		final LinkedCorrectionProposal proposal= new LinkedCorrectionProposal(label, context.getCompilationUnit(), rewrite, IProposalRelevance.INVERSE_BOOLEAN_VARIABLE, image);
2105 		// prepare new variable identifier
2106 		final String oldIdentifier= coveringName.getIdentifier();
2107 		final String notString= Messages.format(CorrectionMessages.AdvancedQuickAssistProcessor_negatedVariableName, ""); //$NON-NLS-1$
2108 		final String newIdentifier;
2109 		if (oldIdentifier.startsWith(notString)) {
2110 			int notLength= notString.length();
2111 			if (oldIdentifier.length() > notLength) {
2112 				newIdentifier= Character.toLowerCase(oldIdentifier.charAt(notLength)) + oldIdentifier.substring(notLength + 1);
2113 			} else {
2114 				newIdentifier= oldIdentifier;
2115 			}
2116 		} else {
2117 			newIdentifier= Messages.format(CorrectionMessages.AdvancedQuickAssistProcessor_negatedVariableName, Character.toUpperCase(oldIdentifier.charAt(0)) + oldIdentifier.substring(1));
2118 		}
2119 		//
2120 		proposal.addLinkedPositionProposal(KEY_NAME, newIdentifier, null);
2121 		proposal.addLinkedPositionProposal(KEY_NAME, oldIdentifier, null);
2122 		// iterate over linked nodes and replace variable references with negated reference
2123 		final HashSet<SimpleName> renamedNames= new HashSet<>();
2124 		for (SimpleName name : linkedNodes) {
2125 			if (renamedNames.contains(name)) {
2126 				continue;
2127 			}
2128 			// prepare new name with new identifier
2129 			SimpleName newName= ast.newSimpleName(newIdentifier);
2130 			proposal.addLinkedPosition(rewrite.track(newName), name == coveringName, KEY_NAME);
2131 			//
2132 			StructuralPropertyDescriptor location= name.getLocationInParent();
2133 			if (location == SingleVariableDeclaration.NAME_PROPERTY) {
2134 				// set new name
2135 				rewrite.replace(name, newName, null);
2136 			} else if (location == Assignment.LEFT_HAND_SIDE_PROPERTY) {
2137 				Assignment assignment= (Assignment) name.getParent();
2138 				Expression expression= assignment.getRightHandSide();
2139 				int exStart= expression.getStartPosition();
2140 				int exEnd= exStart + expression.getLength();
2141 				// collect all names that are used in assignments
2142 				HashSet<SimpleName> overlapNames= new HashSet<>();
2143 				for (SimpleName name2 : linkedNodes) {
2144 					if (name2 == null) {
2145 						continue;
2146 					}
2147 					int name2Start= name2.getStartPosition();
2148 					if (exStart <= name2Start && name2Start < exEnd) {
2149 						overlapNames.add(name2);
2150 					}
2151 				}
2152 				// prepare inverted expression
2153 				SimpleNameRenameProvider provider= new SimpleNameRenameProvider() {
2154 					@Override
2155 					public SimpleName getRenamed(SimpleName simpleName) {
2156 						if (simpleName.resolveBinding() == variableBinding) {
2157 							renamedNames.add(simpleName);
2158 							return ast.newSimpleName(newIdentifier);
2159 						}
2160 						return null;
2161 					}
2162 				};
2163 				Expression inversedExpression= getInversedExpression(rewrite, expression, provider);
2164 				// if any name was not renamed during expression inverting, we can not already rename it, so fail to create assist
2165 				for (SimpleName simpleName : overlapNames) {
2166 					Object o= simpleName;
2167 					if (!renamedNames.contains(o)) {
2168 						return false;
2169 					}
2170 				}
2171 				// check operator and replace if needed
2172 				Assignment.Operator operator= assignment.getOperator();
2173 				if (operator == Assignment.Operator.BIT_AND_ASSIGN) {
2174 					Assignment newAssignment= ast.newAssignment();
2175 					newAssignment.setLeftHandSide(newName);
2176 					newAssignment.setRightHandSide(inversedExpression);
2177 					newAssignment.setOperator(Assignment.Operator.BIT_OR_ASSIGN);
2178 					rewrite.replace(assignment, newAssignment, null);
2179 				} else if (operator == Assignment.Operator.BIT_OR_ASSIGN) {
2180 					Assignment newAssignment= ast.newAssignment();
2181 					newAssignment.setLeftHandSide(newName);
2182 					newAssignment.setRightHandSide(inversedExpression);
2183 					newAssignment.setOperator(Assignment.Operator.BIT_AND_ASSIGN);
2184 					rewrite.replace(assignment, newAssignment, null);
2185 				} else {
2186 					rewrite.replace(expression, inversedExpression, null);
2187 					// set new name
2188 					rewrite.replace(name, newName, null);
2189 				}
2190 			} else if (location == VariableDeclarationFragment.NAME_PROPERTY) {
2191 				// replace initializer for variable
2192 				VariableDeclarationFragment vdf= (VariableDeclarationFragment) name.getParent();
2193 				Expression expression= vdf.getInitializer();
2194 				if (expression != null) {
2195 					rewrite.replace(expression, getInversedExpression(rewrite, expression), null);
2196 				}
2197 				// set new name
2198 				rewrite.replace(name, newName, null);
2199 			} else if (name.getParent() instanceof PrefixExpression && ((PrefixExpression) name.getParent()).getOperator() == PrefixExpression.Operator.NOT) {
2200 				rewrite.replace(name.getParent(), newName, null);
2201 			} else {
2202 				PrefixExpression expression= ast.newPrefixExpression();
2203 				expression.setOperator(PrefixExpression.Operator.NOT);
2204 				expression.setOperand(newName);
2205 				rewrite.replace(name, expression, null);
2206 			}
2207 		}
2208 		// add correction proposal
2209 		resultingCollections.add(proposal);
2210 		return true;
2211 	}
2212 
getPushNegationDownProposals(IInvocationContext context, ASTNode covering, Collection<ICommandAccess> resultingCollections)2213 	private static boolean getPushNegationDownProposals(IInvocationContext context, ASTNode covering, Collection<ICommandAccess> resultingCollections) {
2214 		PrefixExpression negationExpression= null;
2215 		ParenthesizedExpression parenthesizedExpression= null;
2216 		// check for case when cursor is on '!' before parentheses
2217 		if (covering instanceof PrefixExpression) {
2218 			PrefixExpression prefixExpression= (PrefixExpression) covering;
2219 			if (prefixExpression.getOperator() == PrefixExpression.Operator.NOT && prefixExpression.getOperand() instanceof ParenthesizedExpression) {
2220 				negationExpression= prefixExpression;
2221 				parenthesizedExpression= (ParenthesizedExpression) prefixExpression.getOperand();
2222 			}
2223 		}
2224 		// check for case when cursor is on parenthesized expression that is negated
2225 		if (covering instanceof ParenthesizedExpression && covering.getParent() instanceof PrefixExpression && ((PrefixExpression) covering.getParent()).getOperator() == PrefixExpression.Operator.NOT) {
2226 			negationExpression= (PrefixExpression) covering.getParent();
2227 			parenthesizedExpression= (ParenthesizedExpression) covering;
2228 		}
2229 		if (negationExpression == null || (!(parenthesizedExpression.getExpression() instanceof InfixExpression) && !(parenthesizedExpression.getExpression() instanceof ConditionalExpression))) {
2230 			return false;
2231 		}
2232 		//  we could produce quick assist
2233 		if (resultingCollections == null) {
2234 			return true;
2235 		}
2236 		//
2237 		final AST ast= covering.getAST();
2238 		final ASTRewrite rewrite= ASTRewrite.create(ast);
2239 		// prepared inverted expression
2240 		Expression inversedExpression= getInversedExpression(rewrite, parenthesizedExpression.getExpression());
2241 		// check, may be we should keep parentheses
2242 		boolean keepParentheses= false;
2243 		if (negationExpression.getParent() instanceof Expression) {
2244 			int parentPrecedence= OperatorPrecedence.getExpressionPrecedence(((Expression) negationExpression.getParent()));
2245 			int inversedExpressionPrecedence= OperatorPrecedence.getExpressionPrecedence(inversedExpression);
2246 			keepParentheses= parentPrecedence > inversedExpressionPrecedence;
2247 		}
2248 		// replace negated expression with inverted one
2249 		if (keepParentheses) {
2250 			ParenthesizedExpression pe= ast.newParenthesizedExpression();
2251 			pe.setExpression(inversedExpression);
2252 			rewrite.replace(negationExpression, pe, null);
2253 		} else {
2254 			rewrite.replace(negationExpression, inversedExpression, null);
2255 		}
2256 		// add correction proposal
2257 		String label= CorrectionMessages.AdvancedQuickAssistProcessor_pushNegationDown;
2258 		Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
2259 		ASTRewriteCorrectionProposal proposal= new ASTRewriteCorrectionProposal(label, context.getCompilationUnit(), rewrite, IProposalRelevance.PULL_NEGATION_DOWN, image);
2260 		resultingCollections.add(proposal);
2261 		return true;
2262 	}
2263 
getBooleanExpression(ASTNode node)2264 	private static Expression getBooleanExpression(ASTNode node) {
2265 		if (!(node instanceof Expression)) {
2266 			return null;
2267 		}
2268 
2269 		// check if the node is a location where it can be negated
2270 		StructuralPropertyDescriptor locationInParent= node.getLocationInParent();
2271 		if (locationInParent == QualifiedName.NAME_PROPERTY) {
2272 			node= node.getParent();
2273 			locationInParent= node.getLocationInParent();
2274 		}
2275 		while (locationInParent == ParenthesizedExpression.EXPRESSION_PROPERTY) {
2276 			node= node.getParent();
2277 			locationInParent= node.getLocationInParent();
2278 		}
2279 		Expression expression= (Expression) node;
2280 		if (!isBoolean(expression)) {
2281 			return null;
2282 		}
2283 		if (expression.getParent() instanceof InfixExpression) {
2284 			return expression;
2285 		}
2286 		if (locationInParent == Assignment.RIGHT_HAND_SIDE_PROPERTY || locationInParent == IfStatement.EXPRESSION_PROPERTY
2287 				|| locationInParent == WhileStatement.EXPRESSION_PROPERTY || locationInParent == DoStatement.EXPRESSION_PROPERTY
2288 				|| locationInParent == ReturnStatement.EXPRESSION_PROPERTY || locationInParent == ForStatement.EXPRESSION_PROPERTY
2289 				|| locationInParent == AssertStatement.EXPRESSION_PROPERTY || locationInParent == MethodInvocation.ARGUMENTS_PROPERTY
2290 				|| locationInParent == ConstructorInvocation.ARGUMENTS_PROPERTY || locationInParent == SuperMethodInvocation.ARGUMENTS_PROPERTY
2291 				|| locationInParent == EnumConstantDeclaration.ARGUMENTS_PROPERTY || locationInParent == SuperConstructorInvocation.ARGUMENTS_PROPERTY
2292 				|| locationInParent == ClassInstanceCreation.ARGUMENTS_PROPERTY || locationInParent == ConditionalExpression.EXPRESSION_PROPERTY
2293 				|| locationInParent == PrefixExpression.OPERAND_PROPERTY) {
2294 			return expression;
2295 		}
2296 		return null;
2297 	}
2298 
getPullNegationUpProposals(IInvocationContext context, ArrayList<ASTNode> coveredNodes, Collection<ICommandAccess> resultingCollections)2299 	private static boolean getPullNegationUpProposals(IInvocationContext context, ArrayList<ASTNode> coveredNodes, Collection<ICommandAccess> resultingCollections) {
2300 		if (coveredNodes.size() != 1) {
2301 			return false;
2302 		}
2303 		//
2304 		ASTNode fullyCoveredNode= coveredNodes.get(0);
2305 
2306 		Expression expression= getBooleanExpression(fullyCoveredNode);
2307 		if (expression == null || (!(expression instanceof InfixExpression) && !(expression instanceof ConditionalExpression))) {
2308 			return false;
2309 		}
2310 		//  we could produce quick assist
2311 		if (resultingCollections == null) {
2312 			return true;
2313 		}
2314 		//
2315 		AST ast= expression.getAST();
2316 		final ASTRewrite rewrite= ASTRewrite.create(ast);
2317 		// prepared inverted expression
2318 		Expression inversedExpression= getInversedExpression(rewrite, expression);
2319 		// prepare ParenthesizedExpression
2320 		ParenthesizedExpression parenthesizedExpression= ast.newParenthesizedExpression();
2321 		parenthesizedExpression.setExpression(inversedExpression);
2322 		// prepare NOT prefix expression
2323 		PrefixExpression prefixExpression= ast.newPrefixExpression();
2324 		prefixExpression.setOperator(PrefixExpression.Operator.NOT);
2325 		prefixExpression.setOperand(parenthesizedExpression);
2326 		// replace old expression
2327 		rewrite.replace(expression, prefixExpression, null);
2328 		// add correction proposal
2329 		String label= CorrectionMessages.AdvancedQuickAssistProcessor_pullNegationUp;
2330 		Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
2331 		ASTRewriteCorrectionProposal proposal= new ASTRewriteCorrectionProposal(label, context.getCompilationUnit(), rewrite, IProposalRelevance.PULL_NEGATION_UP, image);
2332 		resultingCollections.add(proposal);
2333 		return true;
2334 	}
2335 
getJoinIfListInIfElseIfProposals(IInvocationContext context, ASTNode covering, ArrayList<ASTNode> coveredNodes, Collection<ICommandAccess> resultingCollections)2336 	private static boolean getJoinIfListInIfElseIfProposals(IInvocationContext context, ASTNode covering, ArrayList<ASTNode> coveredNodes, Collection<ICommandAccess> resultingCollections) {
2337 		if (coveredNodes.isEmpty()) {
2338 			return false;
2339 		}
2340 		// check that we have more than one covered statement
2341 		if (coveredNodes.size() < 2) {
2342 			return false;
2343 		}
2344 		// check that all selected nodes are 'if' statements with only 'then' statement
2345 		for (ASTNode node : coveredNodes) {
2346 			if (!(node instanceof IfStatement)) {
2347 				return false;
2348 			}
2349 			IfStatement ifStatement= (IfStatement) node;
2350 			if (ifStatement.getElseStatement() != null) {
2351 				return false;
2352 			}
2353 		}
2354 		//  we could produce quick assist
2355 		if (resultingCollections == null) {
2356 			return true;
2357 		}
2358 		//
2359 		final AST ast= covering.getAST();
2360 		final ASTRewrite rewrite= ASTRewrite.create(ast);
2361 		//
2362 		IfStatement firstIfStatement= (IfStatement) coveredNodes.get(0);
2363 		IfStatement firstNewIfStatement= null;
2364 		//
2365 		IfStatement prevIfStatement= null;
2366 		for (ASTNode astNode : coveredNodes) {
2367 			IfStatement ifStatement= (IfStatement) astNode;
2368 			// prepare new 'if' statement
2369 			IfStatement newIfStatement= ast.newIfStatement();
2370 			newIfStatement.setExpression((Expression) rewrite.createMoveTarget(ifStatement.getExpression()));
2371 			// prepare 'then' statement and convert into block if needed
2372 			Statement thenStatement= (Statement) rewrite.createMoveTarget(ifStatement.getThenStatement());
2373 			if (ifStatement.getThenStatement() instanceof IfStatement) {
2374 				IfStatement ifBodyStatement= (IfStatement) ifStatement.getThenStatement();
2375 				if (ifBodyStatement.getElseStatement() == null) {
2376 					Block thenBlock= ast.newBlock();
2377 					thenBlock.statements().add(thenStatement);
2378 					thenStatement= thenBlock;
2379 				}
2380 			}
2381 			newIfStatement.setThenStatement(thenStatement);
2382 			//
2383 			if (prevIfStatement != null) {
2384 				prevIfStatement.setElseStatement(newIfStatement);
2385 				rewrite.remove(ifStatement, null);
2386 			} else {
2387 				firstNewIfStatement= newIfStatement;
2388 			}
2389 			prevIfStatement= newIfStatement;
2390 		}
2391 		rewrite.replace(firstIfStatement, firstNewIfStatement, null);
2392 		// add correction proposal
2393 		String label= CorrectionMessages.AdvancedQuickAssistProcessor_joinIfSequence;
2394 		Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
2395 		ASTRewriteCorrectionProposal proposal= new ASTRewriteCorrectionProposal(label, context.getCompilationUnit(), rewrite, IProposalRelevance.JOIN_IF_SEQUENCE, image);
2396 		resultingCollections.add(proposal);
2397 		return true;
2398 	}
2399 
getConvertSwitchToIfProposals(IInvocationContext context, ASTNode covering, Collection<ICommandAccess> resultingCollections)2400 	private static boolean getConvertSwitchToIfProposals(IInvocationContext context, ASTNode covering, Collection<ICommandAccess> resultingCollections) {
2401 		if (!(covering instanceof SwitchStatement)) {
2402 			return false;
2403 		}
2404 		//  we could produce quick assist (if all 'case' statements end with 'break')
2405 		if (resultingCollections == null) {
2406 			return true;
2407 		}
2408 		if (!getConvertSwitchToIfProposals(context, covering, resultingCollections, false))
2409 			return false;
2410 		return getConvertSwitchToIfProposals(context, covering, resultingCollections, true);
2411 	}
2412 
getConvertSwitchToIfProposals(IInvocationContext context, ASTNode covering, Collection<ICommandAccess> resultingCollections, boolean preserveNPE)2413 	private static boolean getConvertSwitchToIfProposals(IInvocationContext context, ASTNode covering, Collection<ICommandAccess> resultingCollections, boolean preserveNPE) {
2414 		final AST ast= covering.getAST();
2415 		final ASTRewrite rewrite= ASTRewrite.create(ast);
2416 		final ImportRewrite importRewrite= StubUtility.createImportRewrite(context.getASTRoot(), true);
2417 		//
2418 		SwitchStatement switchStatement= (SwitchStatement) covering;
2419 		ITypeBinding expressionType= switchStatement.getExpression().resolveTypeBinding();
2420 		boolean isStringsInSwitch= expressionType != null && "java.lang.String".equals(expressionType.getQualifiedName()); //$NON-NLS-1$
2421 
2422 		if (!isStringsInSwitch && preserveNPE)
2423 			return false;
2424 
2425 		IfStatement firstIfStatement= null;
2426 		IfStatement currentIfStatement= null;
2427 		Block currentBlock= null;
2428 		boolean hasStopAsLastExecutableStatement= false;
2429 		Block defaultBlock= null;
2430 		Expression currentCondition= null;
2431 		boolean defaultFound= false;
2432 
2433 		ArrayList<Block> allBlocks= new ArrayList<>();
2434 		ImportRewriteContext importRewriteContext= new ContextSensitiveImportRewriteContext(ASTResolving.findParentBodyDeclaration(covering), importRewrite);
2435 
2436 		Expression switchExpression= switchStatement.getExpression();
2437 		Name varName;
2438 		VariableDeclarationStatement variableDeclarationStatement= null;
2439 		if (switchExpression instanceof Name) {
2440 			varName= (Name) switchExpression;
2441 		} else {
2442 			// Switch expression could have side effects, see bug 252040
2443 			VariableDeclarationFragment variableDeclarationFragment= ast.newVariableDeclarationFragment();
2444 			String[] varNames= StubUtility.getVariableNameSuggestions(NamingConventions.VK_LOCAL, context.getCompilationUnit().getJavaProject(), expressionType, switchExpression, null);
2445 			varName= ast.newSimpleName(varNames[0]);
2446 			variableDeclarationFragment.setName((SimpleName) varName);
2447 			variableDeclarationFragment.setStructuralProperty(VariableDeclarationFragment.INITIALIZER_PROPERTY, rewrite.createCopyTarget(switchExpression));
2448 
2449 			variableDeclarationStatement= ast.newVariableDeclarationStatement(variableDeclarationFragment);
2450 			Type type= importRewrite.addImport(expressionType, ast, importRewriteContext, TypeLocation.LOCAL_VARIABLE);
2451 			variableDeclarationStatement.setType(type);
2452 		}
2453 
2454 		for (Iterator<Statement> iter= switchStatement.statements().iterator(); iter.hasNext();) {
2455 			Statement statement= iter.next();
2456 			if (statement instanceof SwitchCase) {
2457 				SwitchCase switchCase= (SwitchCase) statement;
2458 				if (ASTHelper.isSwitchCaseExpressionsSupportedInAST(ast) && switchCase.expressions().size() > 1) {
2459 					return false;
2460 				}
2461 				// special case: pass through
2462 				if (currentBlock != null) {
2463 					if (!hasStopAsLastExecutableStatement) {
2464 						return false;
2465 					}
2466 					currentBlock= null;
2467 				}
2468 
2469 				if (defaultFound) {
2470 					// This gets too complicated. We only support 'default' as last SwitchCase.
2471 					return false;
2472 				}
2473 				if (switchCase.isDefault()) {
2474 					defaultFound= true;
2475 				}
2476 				// prepare condition (is null for 'default')
2477 				Expression switchCaseCondition= createSwitchCaseCondition(ast, rewrite, importRewrite, importRewriteContext, varName, switchCase, isStringsInSwitch, preserveNPE);
2478 				if (currentCondition == null) {
2479 					currentCondition= switchCaseCondition;
2480 				} else {
2481 					InfixExpression condition= ast.newInfixExpression();
2482 					condition.setOperator(InfixExpression.Operator.CONDITIONAL_OR);
2483 					condition.setLeftOperand(currentCondition);
2484 					if (switchCaseCondition == null)
2485 						switchCaseCondition= ast.newBooleanLiteral(true);
2486 					condition.setRightOperand(switchCaseCondition);
2487 					currentCondition= condition;
2488 				}
2489 			} else {
2490 				// ensure that current block exists as 'then' statement of 'if'
2491 				if (currentBlock == null) {
2492 					if (currentCondition != null) {
2493 						IfStatement ifStatement;
2494 						if (firstIfStatement == null) {
2495 							firstIfStatement= ast.newIfStatement();
2496 							ifStatement= firstIfStatement;
2497 						} else {
2498 							ifStatement= ast.newIfStatement();
2499 							currentIfStatement.setElseStatement(ifStatement);
2500 						}
2501 						currentIfStatement= ifStatement;
2502 						ifStatement.setExpression(currentCondition);
2503 						currentCondition= null;
2504 						currentBlock= ast.newBlock();
2505 						ifStatement.setThenStatement(currentBlock);
2506 						allBlocks.add(currentBlock);
2507 					} else {
2508 						// case for default:
2509 						defaultBlock= ast.newBlock();
2510 						currentBlock= defaultBlock;
2511 						allBlocks.add(currentBlock);
2512 						// delay adding of default block
2513 					}
2514 				}
2515 				if (statement instanceof BreakStatement) {
2516 					currentBlock= null;
2517 				} else {
2518 					// add current statement in current block
2519 
2520 					hasStopAsLastExecutableStatement= hasStopAsLastExecutableStatement(statement);
2521 					Statement copyStatement= copyStatementExceptBreak(ast, rewrite, statement);
2522 
2523 					currentBlock.statements().add(copyStatement);
2524 				}
2525 			}
2526 		}
2527 		// check, may be we have delayed default block
2528 		if (defaultBlock != null) {
2529 			currentIfStatement.setElseStatement(defaultBlock);
2530 		}
2531 		// remove unnecessary blocks in blocks
2532 		for (Block block : allBlocks) {
2533 			List<Statement> statements= block.statements();
2534 			if (statements.size() == 1 && statements.get(0) instanceof Block) {
2535 				Block innerBlock= (Block) statements.remove(0);
2536 				block.getParent().setStructuralProperty(block.getLocationInParent(), innerBlock);
2537 			}
2538 		}
2539 
2540 		if (variableDeclarationStatement == null) {
2541 			// replace 'switch' with single if-else-if statement
2542 			rewrite.replace(switchStatement, firstIfStatement, null);
2543 		} else {
2544 			new StatementRewrite(rewrite, new ASTNode[] { switchStatement }).replace(new ASTNode[] { variableDeclarationStatement, firstIfStatement }, null);
2545 		}
2546 
2547 		// add correction proposal
2548 		String source= ASTNodes.asString(switchExpression).replaceAll("\r\n?|\n", " "); //$NON-NLS-1$ //$NON-NLS-2$
2549 		String label= preserveNPE ? Messages.format(CorrectionMessages.AdvancedQuickAssistProcessor_convertSwitchToIf_preserveNPE, source) : CorrectionMessages.AdvancedQuickAssistProcessor_convertSwitchToIf;
2550 		ASTRewriteCorrectionProposal proposal= new ASTRewriteCorrectionProposal(label, context.getCompilationUnit(), rewrite, IProposalRelevance.CONVERT_SWITCH_TO_IF_ELSE);
2551 		proposal.setImportRewrite(importRewrite);
2552 		resultingCollections.add(proposal);
2553 		return true;
2554 	}
2555 
createSwitchCaseCondition(AST ast, ASTRewrite rewrite, ImportRewrite importRewrite, ImportRewriteContext importRewriteContext, Name switchExpression, SwitchCase switchCase, boolean isStringsInSwitch, boolean preserveNPE)2556 	private static Expression createSwitchCaseCondition(AST ast, ASTRewrite rewrite, ImportRewrite importRewrite, ImportRewriteContext importRewriteContext, Name switchExpression,
2557 			SwitchCase switchCase, boolean isStringsInSwitch, boolean preserveNPE) {
2558 		Expression expression= null;
2559 		if (ASTHelper.isSwitchCaseExpressionsSupportedInAST(ast)) {
2560 			if (switchCase.expressions().size() == 1) {
2561 				expression= (Expression) switchCase.expressions().get(0);
2562 			}
2563 		} else {
2564 			expression= switchCase.getExpression();
2565 		}
2566 		if (expression == null)
2567 			return null;
2568 
2569 		if (isStringsInSwitch) {
2570 			MethodInvocation methodInvocation= ast.newMethodInvocation();
2571 			methodInvocation.setName(ast.newSimpleName("equals")); //$NON-NLS-1$
2572 			if (preserveNPE) {
2573 				methodInvocation.setExpression((Expression) rewrite.createStringPlaceholder(switchExpression.getFullyQualifiedName(), ASTNode.QUALIFIED_NAME));
2574 				methodInvocation.arguments().add(rewrite.createCopyTarget(expression));
2575 			} else {
2576 				methodInvocation.setExpression((Expression) rewrite.createCopyTarget(expression));
2577 				methodInvocation.arguments().add(rewrite.createStringPlaceholder(switchExpression.getFullyQualifiedName(), ASTNode.QUALIFIED_NAME));
2578 			}
2579 			return methodInvocation;
2580 		} else {
2581 			InfixExpression condition= ast.newInfixExpression();
2582 			condition.setOperator(InfixExpression.Operator.EQUALS);
2583 			condition.setLeftOperand((Expression) rewrite.createStringPlaceholder(switchExpression.getFullyQualifiedName(), ASTNode.QUALIFIED_NAME));
2584 
2585 			Expression rightExpression= null;
2586 			if (expression instanceof SimpleName && ((SimpleName) expression).resolveBinding() instanceof IVariableBinding) {
2587 				IVariableBinding binding= (IVariableBinding) ((SimpleName) expression).resolveBinding();
2588 				if (binding.isEnumConstant()) {
2589 					String qualifiedName= importRewrite.addImport(binding.getDeclaringClass(), importRewriteContext) + '.' + binding.getName();
2590 					rightExpression= ast.newName(qualifiedName);
2591 				}
2592 			}
2593 			if (rightExpression == null) {
2594 				rightExpression= (Expression) rewrite.createCopyTarget(expression);
2595 			}
2596 			condition.setRightOperand(rightExpression);
2597 			return condition;
2598 		}
2599 	}
2600 
2601 
hasStopAsLastExecutableStatement(Statement lastStatement)2602 	private static boolean hasStopAsLastExecutableStatement(Statement lastStatement) {
2603 		if (lastStatement instanceof ReturnStatement || lastStatement instanceof BreakStatement) {
2604 			return true;
2605 		}
2606 		if (lastStatement instanceof Block) {
2607 			Block block= (Block) lastStatement;
2608 			lastStatement= (Statement) block.statements().get(block.statements().size() - 1);
2609 			return hasStopAsLastExecutableStatement(lastStatement);
2610 		}
2611 		return false;
2612 	}
2613 
copyStatementExceptBreak(AST ast, ASTRewrite rewrite, Statement source)2614 	private static Statement copyStatementExceptBreak(AST ast, ASTRewrite rewrite, Statement source) {
2615 		if (source instanceof Block) {
2616 			Block block= (Block) source;
2617 			Block newBlock= ast.newBlock();
2618 			for (Iterator<Statement> iter= block.statements().iterator(); iter.hasNext();) {
2619 				Statement statement= iter.next();
2620 				if (statement instanceof BreakStatement) {
2621 					continue;
2622 				}
2623 				newBlock.statements().add(copyStatementExceptBreak(ast, rewrite, statement));
2624 			}
2625 			return newBlock;
2626 		}
2627 		return (Statement) rewrite.createMoveTarget(source);
2628 	}
2629 
getConvertIfElseToSwitchProposals(IInvocationContext context, ASTNode coveringNode, ArrayList<ICommandAccess> resultingCollections)2630 	private static boolean getConvertIfElseToSwitchProposals(IInvocationContext context, ASTNode coveringNode, ArrayList<ICommandAccess> resultingCollections) {
2631 		if (!(coveringNode instanceof IfStatement)) {
2632 			return false;
2633 		}
2634 		//  we could produce quick assist
2635 		if (resultingCollections == null) {
2636 			return true;
2637 		}
2638 
2639 		if(!getConvertIfElseToSwitchProposals(context, coveringNode, resultingCollections, true))
2640 			return false;
2641 		return getConvertIfElseToSwitchProposals(context, coveringNode, resultingCollections, false);
2642 	}
2643 
getConvertIfElseToSwitchProposals(IInvocationContext context, ASTNode coveringNode, ArrayList<ICommandAccess> resultingCollections, boolean handleNullArg)2644 	private static boolean getConvertIfElseToSwitchProposals(IInvocationContext context, ASTNode coveringNode, ArrayList<ICommandAccess> resultingCollections, boolean handleNullArg) {
2645 		final AST ast= coveringNode.getAST();
2646 		final ASTRewrite rewrite= ASTRewrite.create(ast);
2647 		final ImportRewrite importRewrite= StubUtility.createImportRewrite(context.getASTRoot(), true);
2648 		ImportRewriteContext importRewriteContext= new ContextSensitiveImportRewriteContext(ASTResolving.findParentBodyDeclaration(coveringNode), importRewrite);
2649 		IfStatement ifStatement= (IfStatement) coveringNode;
2650 		IfStatement currentIf= ifStatement;
2651 		Statement currentStatement= ifStatement;
2652 		Expression currentExpression= currentIf.getExpression();
2653 		SwitchStatement switchStatement= ast.newSwitchStatement();
2654 		Expression switchExpression= null;
2655 		boolean executeDefaultOnNullExpression= false;
2656 		Statement defaultStatement=null;
2657 
2658 		while (currentStatement != null) {
2659 			Expression expression= null;
2660 			List<Expression> caseExpressions= new ArrayList<>();
2661 			if (currentIf != null) {
2662 				while (currentExpression != null) { // loop for fall through cases - multiple expressions with || operator
2663 					Expression leftOperand;
2664 					Expression rightOperand;
2665 					boolean isMethodInvocationCase= false;
2666 					if (currentExpression instanceof MethodInvocation) {
2667 						isMethodInvocationCase= true;
2668 						if (!(((MethodInvocation) currentExpression).getName().getIdentifier()).equals("equals")) //$NON-NLS-1$
2669 							return false;
2670 
2671 						MethodInvocation invocation= (MethodInvocation) currentExpression;
2672 						leftOperand= invocation.getExpression();
2673 						if (leftOperand == null)
2674 							return false;
2675 						ITypeBinding leftBinding= leftOperand.resolveTypeBinding();
2676 						if (leftBinding != null) {
2677 							if (leftBinding.getQualifiedName().equals("java.lang.String")) { //$NON-NLS-1$
2678 								if (!JavaModelUtil.is17OrHigher(context.getCompilationUnit().getJavaProject()))
2679 									return false;
2680 							} else if (!leftBinding.isEnum()) {
2681 								return false;
2682 							}
2683 						}
2684 
2685 						List<Expression> arguments= invocation.arguments();
2686 						if (arguments.size() != 1)
2687 							return false;
2688 						rightOperand= arguments.get(0);
2689 						ITypeBinding rightBinding= leftOperand.resolveTypeBinding();
2690 						if (rightBinding != null) {
2691 							if (rightBinding.getQualifiedName().equals("java.lang.String")) { //$NON-NLS-1$
2692 								if (!JavaModelUtil.is17OrHigher(context.getCompilationUnit().getJavaProject()))
2693 									return false;
2694 							} else if (!rightBinding.isEnum()) {
2695 								return false;
2696 							}
2697 						}
2698 
2699 
2700 					} else if (currentExpression instanceof InfixExpression) {
2701 						InfixExpression infixExpression= (InfixExpression) currentExpression;
2702 						Operator operator= infixExpression.getOperator();
2703 						if (!operator.equals(InfixExpression.Operator.CONDITIONAL_OR)
2704 								&& !operator.equals(InfixExpression.Operator.EQUALS))
2705 							return false;
2706 
2707 						leftOperand= infixExpression.getLeftOperand();
2708 						rightOperand= infixExpression.getRightOperand();
2709 
2710 						if (operator.equals(InfixExpression.Operator.EQUALS)) {
2711 							ITypeBinding typeBinding= leftOperand.resolveTypeBinding();
2712 							if (typeBinding != null && typeBinding.getQualifiedName().equals("java.lang.String")) { //$NON-NLS-1$
2713 								return false; // don't propose quick assist when == is used to compare strings, since switch will use equals()
2714 							}
2715 						} else if (operator.equals(InfixExpression.Operator.CONDITIONAL_OR)) {
2716 							currentExpression= leftOperand;
2717 							continue;
2718 						}
2719 					} else {
2720 						return false;
2721 					}
2722 
2723 					if (leftOperand.resolveConstantExpressionValue() != null) {
2724 						caseExpressions.add(leftOperand);
2725 						expression= rightOperand;
2726 						executeDefaultOnNullExpression|= isMethodInvocationCase;
2727 					} else if (rightOperand.resolveConstantExpressionValue() != null) {
2728 						caseExpressions.add(rightOperand);
2729 						expression= leftOperand;
2730 					} else if (leftOperand instanceof QualifiedName) {
2731 						QualifiedName qualifiedName= (QualifiedName) leftOperand;
2732 						IVariableBinding binding= (IVariableBinding) qualifiedName.resolveBinding();
2733 						if (binding == null || !binding.isEnumConstant())
2734 							return false;
2735 						importRewrite.addImport(binding.getDeclaringClass(), importRewriteContext);
2736 						caseExpressions.add(qualifiedName.getName());
2737 						expression= rightOperand;
2738 						executeDefaultOnNullExpression|= isMethodInvocationCase;
2739 					} else if (rightOperand instanceof QualifiedName) {
2740 						QualifiedName qualifiedName= (QualifiedName) rightOperand;
2741 						IVariableBinding binding= (IVariableBinding) qualifiedName.resolveBinding();
2742 						if (binding == null || !binding.isEnumConstant())
2743 							return false;
2744 						importRewrite.addImport(binding.getDeclaringClass(), importRewriteContext);
2745 						caseExpressions.add(qualifiedName.getName());
2746 						expression= leftOperand;
2747 					} else {
2748 						return false;
2749 					}
2750 					if (expression == null) { // paranoidal check: this condition should never be true
2751 						return false;
2752 					}
2753 
2754 					if (currentExpression.getParent() instanceof InfixExpression) {
2755 						currentExpression= getNextSiblingExpression(currentExpression);
2756 					} else {
2757 						currentExpression= null;
2758 					}
2759 
2760 					if (switchExpression == null) {
2761 						switchExpression= expression;
2762 					}
2763 
2764 					if (!switchExpression.subtreeMatch(new ASTMatcher(), expression)) {
2765 						return false;
2766 					}
2767 				}
2768 			}
2769 
2770 			Statement thenStatement;
2771 			if (currentIf == null) {
2772 				thenStatement= currentStatement; //currentStatement has the default else block
2773 				defaultStatement= currentStatement;
2774 			} else {
2775 				thenStatement= currentIf.getThenStatement();
2776 			}
2777 
2778 			for (SwitchCase switchCaseStatement : createSwitchCaseStatements(ast, rewrite, caseExpressions)) {
2779 				switchStatement.statements().add(switchCaseStatement);
2780 			}
2781 			boolean isBreakRequired= true;
2782 			if (thenStatement instanceof Block) {
2783 				Statement statement= null;
2784 				for (Iterator<Statement> iter= ((Block) thenStatement).statements().iterator(); iter.hasNext();) {
2785 					statement= iter.next();
2786 					switchStatement.statements().add(rewrite.createCopyTarget(statement));
2787 				}
2788 				if (statement instanceof ReturnStatement || statement instanceof ThrowStatement)
2789 					isBreakRequired= false;
2790 			} else {
2791 				if (thenStatement instanceof ReturnStatement || thenStatement instanceof ThrowStatement)
2792 					isBreakRequired= false;
2793 				switchStatement.statements().add(rewrite.createCopyTarget(thenStatement));
2794 			}
2795 			if (isBreakRequired)
2796 				switchStatement.statements().add(ast.newBreakStatement());
2797 
2798 			// advance currentStatement to the next "else if" or "else":
2799 			if (currentIf != null && currentIf.getElseStatement() != null) {
2800 				Statement elseStatement= currentIf.getElseStatement();
2801 				if (elseStatement instanceof IfStatement) {
2802 					currentIf= (IfStatement) elseStatement;
2803 					currentStatement= currentIf;
2804 					currentExpression= currentIf.getExpression();
2805 				} else {
2806 					currentIf= null;
2807 					currentStatement= elseStatement;
2808 					currentExpression= null;
2809 				}
2810 			} else {
2811 				currentStatement= null;
2812 			}
2813 		}
2814 
2815 		if (switchExpression == null)
2816 			return false;
2817 		switchStatement.setExpression((Expression) rewrite.createCopyTarget(switchExpression));
2818 
2819 		if (handleNullArg) {
2820 			if (executeDefaultOnNullExpression) {
2821 				IfStatement newIfStatement= ast.newIfStatement();
2822 
2823 				InfixExpression infixExpression= ast.newInfixExpression();
2824 				infixExpression.setLeftOperand((Expression) rewrite.createCopyTarget(switchExpression));
2825 				infixExpression.setRightOperand(ast.newNullLiteral());
2826 				infixExpression.setOperator(InfixExpression.Operator.EQUALS);
2827 				newIfStatement.setExpression(infixExpression);
2828 
2829 				if (defaultStatement == null) {
2830 					Block block= ast.newBlock();
2831 					newIfStatement.setThenStatement(block);
2832 				} else if (defaultStatement instanceof Block) {
2833 					Block block= ast.newBlock();
2834 					for (Iterator<Statement> iter= ((Block) defaultStatement).statements().iterator(); iter.hasNext();) {
2835 						block.statements().add(rewrite.createCopyTarget(iter.next()));
2836 					}
2837 					newIfStatement.setThenStatement(block);
2838 				} else {
2839 					newIfStatement.setThenStatement((Statement) rewrite.createCopyTarget(defaultStatement));
2840 				}
2841 				Block block= ast.newBlock();
2842 				block.statements().add(switchStatement);
2843 				newIfStatement.setElseStatement(block);
2844 
2845 				rewrite.replace(ifStatement, newIfStatement, null);
2846 
2847 				String source= ASTNodes.asString(switchExpression).replaceAll("\r\n?|\n", " "); //$NON-NLS-1$ //$NON-NLS-2$
2848 				String label= Messages.format(CorrectionMessages.AdvancedQuickAssistProcessor_convertIfElseToSwitch_handleNullArg, source);
2849 				ASTRewriteCorrectionProposal proposal= new ASTRewriteCorrectionProposal(label, context.getCompilationUnit(), rewrite, IProposalRelevance.CONVERT_IF_ELSE_TO_SWITCH);
2850 				proposal.setImportRewrite(importRewrite);
2851 				resultingCollections.add(proposal);
2852 			}
2853 		} else {
2854 			rewrite.replace(ifStatement, switchStatement, null);
2855 
2856 			String label= CorrectionMessages.AdvancedQuickAssistProcessor_convertIfElseToSwitch;
2857 			ASTRewriteCorrectionProposal proposal= new ASTRewriteCorrectionProposal(label, context.getCompilationUnit(), rewrite, IProposalRelevance.CONVERT_IF_ELSE_TO_SWITCH);
2858 			proposal.setImportRewrite(importRewrite);
2859 			resultingCollections.add(proposal);
2860 		}
2861 		return true;
2862 	}
2863 
getNextSiblingExpression(Expression expression)2864 	private static Expression getNextSiblingExpression(Expression expression) {
2865 		InfixExpression parentInfixExpression= (InfixExpression) expression.getParent();
2866 		List<Expression> extendedOperands= parentInfixExpression.extendedOperands();
2867 		Expression sibiling;
2868 		if (expression.equals(parentInfixExpression.getLeftOperand())) {
2869 			sibiling= parentInfixExpression.getRightOperand();
2870 		} else if (expression.equals(parentInfixExpression.getRightOperand())) {
2871 			if (parentInfixExpression.getParent() instanceof InfixExpression) {
2872 				sibiling= getNextSiblingExpression(parentInfixExpression);
2873 			} else if (extendedOperands.size() > 0) {
2874 				sibiling= extendedOperands.get(0);
2875 			} else {
2876 				sibiling= null;
2877 			}
2878 		} else if (extendedOperands.contains(expression)) {
2879 			sibiling= null;
2880 			ListIterator<Expression> it= extendedOperands.listIterator();
2881 			while (it.hasNext()) {
2882 				if (expression.equals(it.next()) && it.hasNext()) {
2883 					sibiling= it.next();
2884 					break;
2885 				}
2886 			}
2887 		} else {
2888 			sibiling= null;
2889 		}
2890 		return sibiling;
2891 	}
2892 
createSwitchCaseStatements(AST ast, ASTRewrite rewrite, List<Expression> caseExpressions)2893 	private static SwitchCase[] createSwitchCaseStatements(AST ast, ASTRewrite rewrite, List<Expression> caseExpressions) {
2894 		int len= (caseExpressions.isEmpty()) ? 1 : caseExpressions.size();
2895 		SwitchCase[] switchCaseStatements= new SwitchCase[len];
2896 		if (caseExpressions.isEmpty()) {
2897 			switchCaseStatements[0]= ast.newSwitchCase();
2898 			if (!ASTHelper.isSwitchCaseExpressionsSupportedInAST(ast)) {
2899 				switchCaseStatements[0].setExpression(null);
2900 			}
2901 		} else {
2902 			for (int i= 0; i < caseExpressions.size(); i++) {
2903 				ASTNode astNode= caseExpressions.get(i);
2904 				switchCaseStatements[i]= ast.newSwitchCase();
2905 				Expression copyTarget= (Expression) rewrite.createCopyTarget(astNode);
2906 				if (ASTHelper.isSwitchCaseExpressionsSupportedInAST(ast)) {
2907 					switchCaseStatements[i].expressions().add(copyTarget);
2908 				} else {
2909 					switchCaseStatements[i].setExpression(copyTarget);
2910 				}
2911 			}
2912 		}
2913 		return switchCaseStatements;
2914 	}
2915 
2916 }
2917