]>
Commit | Line | Data |
---|---|---|
1b2798f6 EK |
1 | /******************************************************************************* |
2 | * Copyright (c) 2011 IBM Corporation and others. | |
3 | * All rights reserved. This program and the accompanying materials | |
4 | * are made available under the terms of the Eclipse Public License v1.0 | |
5 | * which accompanies this distribution, and is available at | |
6 | * http://www.eclipse.org/legal/epl-v10.html | |
7 | * | |
8 | * Contributors: | |
9 | * IBM Corporation - initial API and implementation | |
10 | *******************************************************************************/ | |
11 | ||
12 | package org.eclipse.jdt.internal.corext.dom; | |
13 | ||
14 | import java.util.Iterator; | |
15 | ||
16 | import org.eclipse.jdt.core.dom.ASTNode; | |
17 | import org.eclipse.jdt.core.dom.ArrayAccess; | |
18 | import org.eclipse.jdt.core.dom.AssertStatement; | |
19 | import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor; | |
20 | import org.eclipse.jdt.core.dom.ConditionalExpression; | |
21 | import org.eclipse.jdt.core.dom.DoStatement; | |
22 | import org.eclipse.jdt.core.dom.EnhancedForStatement; | |
23 | import org.eclipse.jdt.core.dom.Expression; | |
24 | import org.eclipse.jdt.core.dom.ForStatement; | |
25 | import org.eclipse.jdt.core.dom.ITypeBinding; | |
26 | import org.eclipse.jdt.core.dom.IfStatement; | |
27 | import org.eclipse.jdt.core.dom.InfixExpression; | |
28 | import org.eclipse.jdt.core.dom.InfixExpression.Operator; | |
29 | import org.eclipse.jdt.core.dom.ParenthesizedExpression; | |
30 | import org.eclipse.jdt.core.dom.ReturnStatement; | |
31 | import org.eclipse.jdt.core.dom.SingleVariableDeclaration; | |
32 | import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor; | |
33 | import org.eclipse.jdt.core.dom.SwitchCase; | |
34 | import org.eclipse.jdt.core.dom.SwitchStatement; | |
35 | import org.eclipse.jdt.core.dom.SynchronizedStatement; | |
36 | import org.eclipse.jdt.core.dom.ThrowStatement; | |
37 | import org.eclipse.jdt.core.dom.VariableDeclarationFragment; | |
38 | import org.eclipse.jdt.core.dom.WhileStatement; | |
39 | ||
40 | import org.eclipse.jdt.internal.corext.refactoring.code.OperatorPrecedence; | |
41 | ||
42 | /** | |
43 | * Helper class to check if an expression requires parentheses. | |
44 | * | |
45 | * @since 3.7 | |
46 | */ | |
47 | public class NecessaryParenthesesChecker { | |
48 | ||
49 | /* | |
50 | * Get the expression wrapped by the parentheses | |
51 | * i.e. ((((expression)))) -> expression | |
52 | */ | |
53 | private static Expression getExpression(ParenthesizedExpression node) { | |
54 | Expression expression= node.getExpression(); | |
55 | while (expression instanceof ParenthesizedExpression) { | |
56 | expression= ((ParenthesizedExpression)expression).getExpression(); | |
57 | } | |
58 | return expression; | |
59 | } | |
60 | ||
61 | private static boolean expressionTypeNeedsParentheses(Expression expression) { | |
62 | int type= expression.getNodeType(); | |
63 | return type == ASTNode.INFIX_EXPRESSION | |
64 | || type == ASTNode.CONDITIONAL_EXPRESSION | |
65 | || type == ASTNode.PREFIX_EXPRESSION | |
66 | || type == ASTNode.POSTFIX_EXPRESSION | |
67 | || type == ASTNode.CAST_EXPRESSION | |
68 | || type == ASTNode.INSTANCEOF_EXPRESSION | |
69 | || type == ASTNode.ARRAY_CREATION | |
70 | || type == ASTNode.ASSIGNMENT; | |
71 | } | |
72 | ||
73 | private static boolean locationNeedsParentheses(StructuralPropertyDescriptor locationInParent) { | |
74 | if (locationInParent instanceof ChildListPropertyDescriptor && locationInParent != InfixExpression.EXTENDED_OPERANDS_PROPERTY) { | |
75 | // e.g. argument lists of MethodInvocation, ClassInstanceCreation, dimensions of ArrayCreation ... | |
76 | return false; | |
77 | } | |
78 | if (locationInParent == VariableDeclarationFragment.INITIALIZER_PROPERTY | |
79 | || locationInParent == SingleVariableDeclaration.INITIALIZER_PROPERTY | |
80 | || locationInParent == ReturnStatement.EXPRESSION_PROPERTY | |
81 | || locationInParent == EnhancedForStatement.EXPRESSION_PROPERTY | |
82 | || locationInParent == ForStatement.EXPRESSION_PROPERTY | |
83 | || locationInParent == WhileStatement.EXPRESSION_PROPERTY | |
84 | || locationInParent == DoStatement.EXPRESSION_PROPERTY | |
85 | || locationInParent == AssertStatement.EXPRESSION_PROPERTY | |
86 | || locationInParent == AssertStatement.MESSAGE_PROPERTY | |
87 | || locationInParent == IfStatement.EXPRESSION_PROPERTY | |
88 | || locationInParent == SwitchStatement.EXPRESSION_PROPERTY | |
89 | || locationInParent == SwitchCase.EXPRESSION_PROPERTY | |
90 | || locationInParent == ArrayAccess.INDEX_PROPERTY | |
91 | || locationInParent == ThrowStatement.EXPRESSION_PROPERTY | |
92 | || locationInParent == SynchronizedStatement.EXPRESSION_PROPERTY | |
93 | || locationInParent == ParenthesizedExpression.EXPRESSION_PROPERTY) { | |
94 | return false; | |
95 | } | |
96 | return true; | |
97 | } | |
98 | ||
99 | /* | |
100 | * Do all operands in expression have same type | |
101 | */ | |
102 | private static boolean isAllOperandsHaveSameType(InfixExpression expression) { | |
103 | ITypeBinding binding= expression.getLeftOperand().resolveTypeBinding(); | |
104 | if (binding == null) | |
105 | return false; | |
106 | ||
107 | ITypeBinding current= expression.getRightOperand().resolveTypeBinding(); | |
108 | if (binding != current) | |
109 | return false; | |
110 | ||
111 | for (Iterator<Expression> iterator= expression.extendedOperands().iterator(); iterator.hasNext();) { | |
112 | Expression operand= iterator.next(); | |
113 | current= operand.resolveTypeBinding(); | |
114 | if (binding != current) | |
115 | return false; | |
116 | } | |
117 | ||
118 | return true; | |
119 | } | |
120 | ||
121 | /* | |
122 | * Is the expression of integer type | |
123 | */ | |
124 | private static boolean isExpressionIntegerType(Expression expression) { | |
125 | ITypeBinding binding= expression.resolveTypeBinding(); | |
126 | if (binding == null) | |
127 | return false; | |
128 | ||
129 | if (!binding.isPrimitive()) | |
130 | return false; | |
131 | ||
132 | String name= binding.getName(); | |
133 | if (isIntegerNumber(name)) | |
134 | return true; | |
135 | ||
136 | return false; | |
137 | } | |
138 | ||
139 | private static boolean isExpressionStringType(Expression expression) { | |
140 | ITypeBinding binding= expression.resolveTypeBinding(); | |
141 | if (binding == null) | |
142 | return false; | |
143 | ||
144 | return "java.lang.String".equals(binding.getQualifiedName()); //$NON-NLS-1$ | |
145 | } | |
146 | ||
147 | /* | |
148 | * Is the given expression associative? | |
149 | * | |
150 | * This is true if and only if:<br> | |
151 | * <code>left operator (right) == (right) operator left == right operator left</code> | |
152 | */ | |
153 | private static boolean isAssociative(InfixExpression expression) { | |
154 | Operator operator= expression.getOperator(); | |
155 | ||
156 | if (operator == InfixExpression.Operator.PLUS) | |
157 | return isExpressionStringType(expression) || isExpressionIntegerType(expression) && isAllOperandsHaveSameType(expression); | |
158 | ||
159 | if (operator == InfixExpression.Operator.TIMES) | |
160 | return isExpressionIntegerType(expression) && isAllOperandsHaveSameType(expression); | |
161 | ||
162 | if (operator == InfixExpression.Operator.CONDITIONAL_AND | |
163 | || operator == InfixExpression.Operator.CONDITIONAL_OR | |
164 | || operator == InfixExpression.Operator.AND | |
165 | || operator == InfixExpression.Operator.OR | |
166 | || operator == InfixExpression.Operator.XOR) | |
167 | return true; | |
168 | ||
169 | return false; | |
170 | } | |
171 | ||
172 | private static boolean isIntegerNumber(String name) { | |
173 | return "int".equals(name) || "long".equals(name) || "byte".equals(name) || "char".equals(name) || "short".equals(name); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ | |
174 | } | |
175 | ||
176 | private static boolean needsParenthesesInInfixExpression(Expression expression, InfixExpression parentInfix, StructuralPropertyDescriptor locationInParent) { | |
177 | if (locationInParent == InfixExpression.LEFT_OPERAND_PROPERTY) { | |
178 | //we have (expr op expr) op expr | |
179 | //infix expressions are evaluated from left to right -> parentheses not needed | |
180 | return false; | |
181 | } else if (isAssociative(parentInfix)) { | |
182 | //we have parent op (expr op expr) and op is associative | |
183 | //left op (right) == (right) op left == right op left | |
184 | if (expression instanceof InfixExpression) { | |
185 | InfixExpression infixExpression= (InfixExpression)expression; | |
186 | Operator operator= infixExpression.getOperator(); | |
187 | ||
188 | if (isExpressionStringType(parentInfix)) { | |
189 | if (parentInfix.getOperator() == InfixExpression.Operator.PLUS && operator == InfixExpression.Operator.PLUS && isExpressionStringType(infixExpression)) { | |
190 | // 1 + ("" + 2) == 1 + "" + 2 | |
191 | // 1 + (2 + "") != 1 + 2 + "" | |
192 | // "" + (2 + "") == "" + 2 + "" | |
193 | return !isExpressionStringType(infixExpression.getLeftOperand()) && !isExpressionStringType(parentInfix.getLeftOperand()); | |
194 | } | |
195 | //"" + (1 + 2), "" + (1 - 2) etc | |
196 | return true; | |
197 | } | |
198 | ||
199 | if (parentInfix.getOperator() != InfixExpression.Operator.TIMES) | |
200 | return false; | |
201 | ||
202 | if (operator == InfixExpression.Operator.TIMES) | |
203 | // x * (y * z) == x * y * z | |
204 | return false; | |
205 | ||
206 | if (operator == InfixExpression.Operator.REMAINDER || operator == InfixExpression.Operator.DIVIDE) | |
207 | // x * (y % z) != x * y % z , x * (y / z) == x * y / z rounding involved | |
208 | return true; | |
209 | ||
210 | return false; | |
211 | } | |
212 | return false; | |
213 | } else { | |
214 | return true; | |
215 | } | |
216 | } | |
217 | ||
218 | /** | |
219 | * Can the parentheses be removed from the parenthesized expression ? | |
220 | * | |
221 | * <p> | |
222 | * <b>Note:</b> The parenthesized expression must not be an unparented node. | |
223 | * </p> | |
224 | * | |
225 | * @param expression the parenthesized expression | |
226 | * @return <code>true</code> if parentheses can be removed, <code>false</code> otherwise. | |
227 | */ | |
228 | public static boolean canRemoveParentheses(Expression expression) { | |
229 | return canRemoveParentheses(expression, expression.getParent(), expression.getLocationInParent()); | |
230 | } | |
231 | ||
232 | /** | |
233 | * Can the parentheses be removed from the parenthesized expression when inserted into | |
234 | * <code>parent</code> at <code>locationInParent</code> ? | |
235 | * | |
236 | * <p> | |
237 | * <b>Note:</b> The parenthesized expression can be an unparented node. | |
238 | * </p> | |
239 | * | |
240 | * @param expression the parenthesized expression | |
241 | * @param parent the parent node | |
242 | * @param locationInParent location of expression in the parent | |
243 | * @return <code>true</code> if parentheses can be removed, <code>false</code> otherwise. | |
244 | */ | |
245 | public static boolean canRemoveParentheses(Expression expression, ASTNode parent, StructuralPropertyDescriptor locationInParent) { | |
246 | if (!(expression instanceof ParenthesizedExpression)) { | |
247 | return false; | |
248 | } | |
249 | return !needsParentheses(getExpression((ParenthesizedExpression)expression), parent, locationInParent); | |
250 | } | |
251 | ||
252 | /** | |
253 | * Does the <code>expression</code> need parentheses when inserted into <code>parent</code> at | |
254 | * <code>locationInParent</code> ? | |
255 | * | |
256 | * <p> | |
257 | * <b>Note:</b> The expression can be an unparented node. | |
258 | * </p> | |
259 | * | |
260 | * @param expression the expression | |
261 | * @param parent the parent node | |
262 | * @param locationInParent location of expression in the parent | |
263 | * @return <code>true</code> if the expression needs parentheses, <code>false</code> otherwise. | |
264 | */ | |
265 | public static boolean needsParentheses(Expression expression, ASTNode parent, StructuralPropertyDescriptor locationInParent) { | |
266 | if (!expressionTypeNeedsParentheses(expression)) | |
267 | return false; | |
268 | ||
269 | if (!locationNeedsParentheses(locationInParent)) { | |
270 | return false; | |
271 | } | |
272 | ||
273 | if (parent instanceof Expression) { | |
274 | Expression parentExpression= (Expression)parent; | |
275 | ||
276 | int expressionPrecedence= OperatorPrecedence.getExpressionPrecedence(expression); | |
277 | int parentPrecedence= OperatorPrecedence.getExpressionPrecedence(parentExpression); | |
278 | ||
279 | if (expressionPrecedence > parentPrecedence) | |
280 | //(opEx) opParent and opEx binds more -> parentheses not needed | |
281 | return false; | |
282 | ||
283 | if (expressionPrecedence < parentPrecedence) | |
284 | //(opEx) opParent and opEx binds less -> parentheses needed | |
285 | return true; | |
286 | ||
287 | //(opEx) opParent binds equal | |
288 | ||
289 | if (parentExpression instanceof InfixExpression) { | |
290 | return needsParenthesesInInfixExpression(expression, (InfixExpression)parentExpression, locationInParent); | |
291 | } | |
292 | ||
293 | if (parentExpression instanceof ConditionalExpression && locationInParent == ConditionalExpression.EXPRESSION_PROPERTY) { | |
294 | return true; | |
295 | } | |
296 | ||
297 | return false; | |
298 | } | |
299 | ||
300 | return true; | |
301 | } | |
302 | } |