001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018/////////////////////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.coding; 021 022import java.util.ArrayList; 023import java.util.Collections; 024import java.util.List; 025import java.util.regex.Pattern; 026 027import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 028import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 029import com.puppycrawl.tools.checkstyle.api.DetailAST; 030import com.puppycrawl.tools.checkstyle.api.TokenTypes; 031import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 032import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 033 034/** 035 * <div> 036 * Checks if unnecessary parentheses are used in a statement or expression. 037 * The check will flag the following with warnings: 038 * </div> 039 * <div class="wrapper"><pre class="prettyprint"><code class="language-java"> 040 * return (x); // parens around identifier 041 * return (x + 1); // parens around return value 042 * int x = (y / 2 + 1); // parens around assignment rhs 043 * for (int i = (0); i < 10; i++) { // parens around literal 044 * t -= (z + 1); // parens around assignment rhs 045 * boolean a = (x > 7 && y > 5) // parens around expression 046 * || z < 9; 047 * boolean b = (~a) > -27 // parens around ~a 048 * && (a-- < 30); // parens around expression 049 * </code></pre></div> 050 * 051 * <p> 052 * Notes: 053 * The check is not "type aware", that is to say, it can't tell if parentheses 054 * are unnecessary based on the types in an expression. The check is partially aware about 055 * operator precedence but unaware about operator associativity. 056 * It won't catch cases such as: 057 * </p> 058 * {@snippet lang="text" : 059 * int x = (a + b) + c; // 1st Case 060 * boolean p = true; // 2nd Case 061 * int q = 4; 062 * int r = 3; 063 * if (p == (q <= r)) {} 064 * } 065 * 066 * <p> 067 * In the first case, given that <em>a</em>, <em>b</em>, and <em>c</em> are 068 * all {@code int} variables, the parentheses around {@code a + b} are not needed. 069 * In the second case, parentheses are required as <em>q</em>, <em>r</em> are 070 * of type {@code int} and <em>p</em> is of type {@code boolean} 071 * and removing parentheses will give a compile-time error. Even if <em>q</em> 072 * and <em>r</em> were {@code boolean} still there will be no violation 073 * raised as check is not "type aware". 074 * </p> 075 * 076 * <p> 077 * The partial support for operator precedence includes cases of the following type: 078 * </p> 079 * {@snippet lang="text" : 080 * boolean a = true, b = true; 081 * boolean c = false, d = false; 082 * if ((a && b) || c) { // violation, unnecessary paren 083 * } 084 * if (a && (b || c)) { // ok 085 * } 086 * if ((a == b) && c) { // violation, unnecessary paren 087 * } 088 * String e = "e"; 089 * if ((e instanceof String) && a || b) { // violation, unnecessary paren 090 * } 091 * int f = 0; 092 * int g = 0; 093 * if (!(f >= g) // ok 094 * && (g > f)) { // violation, unnecessary paren 095 * } 096 * if ((++f) > g && a) { // violation, unnecessary paren 097 * } 098 * } 099 * 100 * @since 3.4 101 */ 102@FileStatefulCheck 103public class UnnecessaryParenthesesCheck extends AbstractCheck { 104 105 /** 106 * A key is pointing to the warning message text in "messages.properties" file. 107 */ 108 public static final String MSG_IDENT = "unnecessary.paren.ident"; 109 110 /** 111 * A key is pointing to the warning message text in "messages.properties" file. 112 */ 113 public static final String MSG_ASSIGN = "unnecessary.paren.assign"; 114 115 /** 116 * A key is pointing to the warning message text in "messages.properties" file. 117 */ 118 public static final String MSG_EXPR = "unnecessary.paren.expr"; 119 120 /** 121 * A key is pointing to the warning message text in "messages.properties" file. 122 */ 123 public static final String MSG_LITERAL = "unnecessary.paren.literal"; 124 125 /** 126 * A key is pointing to the warning message text in "messages.properties" file. 127 */ 128 public static final String MSG_STRING = "unnecessary.paren.string"; 129 130 /** 131 * A key is pointing to the warning message text in "messages.properties" file. 132 */ 133 public static final String MSG_RETURN = "unnecessary.paren.return"; 134 135 /** 136 * A key is pointing to the warning message text in "messages.properties" file. 137 */ 138 public static final String MSG_LAMBDA = "unnecessary.paren.lambda"; 139 140 /** 141 * Compiled pattern used to match newline control characters, for replacement. 142 */ 143 private static final Pattern NEWLINE = Pattern.compile("\\R"); 144 145 /** 146 * String used to amend TEXT_BLOCK_CONTENT so that it matches STRING_LITERAL. 147 */ 148 private static final String QUOTE = "\""; 149 150 /** The maximum string length before we chop the string. */ 151 private static final int MAX_QUOTED_LENGTH = 25; 152 153 /** Token types for literals. */ 154 private static final int[] LITERALS = { 155 TokenTypes.NUM_DOUBLE, 156 TokenTypes.NUM_FLOAT, 157 TokenTypes.NUM_INT, 158 TokenTypes.NUM_LONG, 159 TokenTypes.STRING_LITERAL, 160 TokenTypes.LITERAL_NULL, 161 TokenTypes.LITERAL_FALSE, 162 TokenTypes.LITERAL_TRUE, 163 TokenTypes.TEXT_BLOCK_LITERAL_BEGIN, 164 }; 165 166 /** Token types for assignment operations. */ 167 private static final int[] ASSIGNMENTS = { 168 TokenTypes.ASSIGN, 169 TokenTypes.BAND_ASSIGN, 170 TokenTypes.BOR_ASSIGN, 171 TokenTypes.BSR_ASSIGN, 172 TokenTypes.BXOR_ASSIGN, 173 TokenTypes.DIV_ASSIGN, 174 TokenTypes.MINUS_ASSIGN, 175 TokenTypes.MOD_ASSIGN, 176 TokenTypes.PLUS_ASSIGN, 177 TokenTypes.SL_ASSIGN, 178 TokenTypes.SR_ASSIGN, 179 TokenTypes.STAR_ASSIGN, 180 }; 181 182 /** Token types for conditional operators. */ 183 private static final int[] CONDITIONAL_OPERATOR = { 184 TokenTypes.LOR, 185 TokenTypes.LAND, 186 }; 187 188 /** Token types for relation operator. */ 189 private static final int[] RELATIONAL_OPERATOR = { 190 TokenTypes.LITERAL_INSTANCEOF, 191 TokenTypes.GT, 192 TokenTypes.LT, 193 TokenTypes.GE, 194 TokenTypes.LE, 195 TokenTypes.EQUAL, 196 TokenTypes.NOT_EQUAL, 197 }; 198 199 /** Token types for unary and postfix operators. */ 200 private static final int[] UNARY_AND_POSTFIX = { 201 TokenTypes.UNARY_MINUS, 202 TokenTypes.UNARY_PLUS, 203 TokenTypes.INC, 204 TokenTypes.DEC, 205 TokenTypes.LNOT, 206 TokenTypes.BNOT, 207 TokenTypes.POST_INC, 208 TokenTypes.POST_DEC, 209 }; 210 211 /** Types of tokens with higher priority than unary operators. */ 212 private static final int[] ARRAY_AND_FIELD_ACCESS = { 213 TokenTypes.INDEX_OP, 214 TokenTypes.DOT, 215 TokenTypes.LITERAL_NEW, 216 }; 217 218 /** Token types for bitwise binary operator. */ 219 private static final int[] BITWISE_BINARY_OPERATORS = { 220 TokenTypes.BXOR, 221 TokenTypes.BOR, 222 TokenTypes.BAND, 223 }; 224 225 /** 226 * Used to test if logging a warning in a parent node may be skipped 227 * because a warning was already logged on an immediate child node. 228 */ 229 private DetailAST parentToSkip; 230 /** Depth of nested assignments. Normally this will be 0 or 1. */ 231 private int assignDepth; 232 233 /** 234 * Creates a new {@code UnnecessaryParenthesesCheck} instance. 235 */ 236 public UnnecessaryParenthesesCheck() { 237 // no code by default 238 } 239 240 @Override 241 public int[] getDefaultTokens() { 242 return new int[] { 243 TokenTypes.EXPR, 244 TokenTypes.IDENT, 245 TokenTypes.NUM_DOUBLE, 246 TokenTypes.NUM_FLOAT, 247 TokenTypes.NUM_INT, 248 TokenTypes.NUM_LONG, 249 TokenTypes.STRING_LITERAL, 250 TokenTypes.LITERAL_NULL, 251 TokenTypes.LITERAL_FALSE, 252 TokenTypes.LITERAL_TRUE, 253 TokenTypes.ASSIGN, 254 TokenTypes.BAND_ASSIGN, 255 TokenTypes.BOR_ASSIGN, 256 TokenTypes.BSR_ASSIGN, 257 TokenTypes.BXOR_ASSIGN, 258 TokenTypes.DIV_ASSIGN, 259 TokenTypes.MINUS_ASSIGN, 260 TokenTypes.MOD_ASSIGN, 261 TokenTypes.PLUS_ASSIGN, 262 TokenTypes.SL_ASSIGN, 263 TokenTypes.SR_ASSIGN, 264 TokenTypes.STAR_ASSIGN, 265 TokenTypes.LAMBDA, 266 TokenTypes.TEXT_BLOCK_LITERAL_BEGIN, 267 TokenTypes.LAND, 268 TokenTypes.LOR, 269 TokenTypes.LITERAL_INSTANCEOF, 270 TokenTypes.GT, 271 TokenTypes.LT, 272 TokenTypes.GE, 273 TokenTypes.LE, 274 TokenTypes.EQUAL, 275 TokenTypes.NOT_EQUAL, 276 TokenTypes.UNARY_MINUS, 277 TokenTypes.UNARY_PLUS, 278 TokenTypes.INC, 279 TokenTypes.DEC, 280 TokenTypes.LNOT, 281 TokenTypes.BNOT, 282 TokenTypes.POST_INC, 283 TokenTypes.POST_DEC, 284 TokenTypes.INDEX_OP, 285 TokenTypes.DOT, 286 TokenTypes.TYPECAST, 287 }; 288 } 289 290 @Override 291 public int[] getAcceptableTokens() { 292 return new int[] { 293 TokenTypes.EXPR, 294 TokenTypes.IDENT, 295 TokenTypes.NUM_DOUBLE, 296 TokenTypes.NUM_FLOAT, 297 TokenTypes.NUM_INT, 298 TokenTypes.NUM_LONG, 299 TokenTypes.STRING_LITERAL, 300 TokenTypes.LITERAL_NULL, 301 TokenTypes.LITERAL_FALSE, 302 TokenTypes.LITERAL_TRUE, 303 TokenTypes.ASSIGN, 304 TokenTypes.BAND_ASSIGN, 305 TokenTypes.BOR_ASSIGN, 306 TokenTypes.BSR_ASSIGN, 307 TokenTypes.BXOR_ASSIGN, 308 TokenTypes.DIV_ASSIGN, 309 TokenTypes.MINUS_ASSIGN, 310 TokenTypes.MOD_ASSIGN, 311 TokenTypes.PLUS_ASSIGN, 312 TokenTypes.SL_ASSIGN, 313 TokenTypes.SR_ASSIGN, 314 TokenTypes.STAR_ASSIGN, 315 TokenTypes.LAMBDA, 316 TokenTypes.TEXT_BLOCK_LITERAL_BEGIN, 317 TokenTypes.LAND, 318 TokenTypes.LOR, 319 TokenTypes.LITERAL_INSTANCEOF, 320 TokenTypes.GT, 321 TokenTypes.LT, 322 TokenTypes.GE, 323 TokenTypes.LE, 324 TokenTypes.EQUAL, 325 TokenTypes.NOT_EQUAL, 326 TokenTypes.UNARY_MINUS, 327 TokenTypes.UNARY_PLUS, 328 TokenTypes.INC, 329 TokenTypes.DEC, 330 TokenTypes.LNOT, 331 TokenTypes.BNOT, 332 TokenTypes.POST_INC, 333 TokenTypes.POST_DEC, 334 TokenTypes.BXOR, 335 TokenTypes.BOR, 336 TokenTypes.BAND, 337 TokenTypes.QUESTION, 338 TokenTypes.INDEX_OP, 339 TokenTypes.DOT, 340 TokenTypes.LITERAL_NEW, 341 TokenTypes.TYPECAST, 342 }; 343 } 344 345 @Override 346 public int[] getRequiredTokens() { 347 // Check can work with any of acceptable tokens 348 return CommonUtil.EMPTY_INT_ARRAY; 349 } 350 351 // -@cs[CyclomaticComplexity] All logs should be in visit token. 352 @Override 353 public void visitToken(DetailAST ast) { 354 final DetailAST parent = ast.getParent(); 355 356 if (isLambdaSingleParameterSurrounded(ast)) { 357 log(ast, MSG_LAMBDA); 358 } 359 else if (ast.getType() == TokenTypes.QUESTION) { 360 getParenthesesChildrenAroundQuestion(ast) 361 .forEach(unnecessaryChild -> log(unnecessaryChild, MSG_EXPR)); 362 } 363 else if (parent.getType() != TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) { 364 final int type = ast.getType(); 365 final boolean surrounded = isSurrounded(getSelfOrParentMethodCall(ast)); 366 // An identifier surrounded by parentheses. 367 if (surrounded && type == TokenTypes.IDENT) { 368 parentToSkip = ast.getParent(); 369 log(ast, MSG_IDENT, ast.getText()); 370 } 371 // A literal (numeric or string) surrounded by parentheses. 372 else if (surrounded && TokenUtil.isOfType(type, LITERALS)) { 373 parentToSkip = ast.getParent(); 374 logLiteral(ast, type); 375 } 376 // The rhs of an assignment surrounded by parentheses. 377 else if (TokenUtil.isOfType(type, ASSIGNMENTS)) { 378 assignDepth++; 379 final DetailAST last = ast.getLastChild(); 380 if (last.getType() == TokenTypes.RPAREN) { 381 log(ast, MSG_ASSIGN); 382 } 383 } 384 // A type cast surrounded by parentheses. 385 else if (surrounded && type == TokenTypes.TYPECAST) { 386 logUnnecessaryTypeCast(ast); 387 } 388 } 389 } 390 391 /** 392 * Logs the appropriate message for a parenthesized literal. 393 * 394 * @param ast the literal token 395 * @param type the token type 396 */ 397 private void logLiteral(DetailAST ast, int type) { 398 if (type == TokenTypes.STRING_LITERAL) { 399 log(ast, MSG_STRING, 400 chopString(ast.getText())); 401 } 402 else if (type == TokenTypes.TEXT_BLOCK_LITERAL_BEGIN) { 403 // Strip newline control characters to keep message as single-line, add 404 // quotes to make string consistent with STRING_LITERAL 405 final String logString = QUOTE 406 + NEWLINE.matcher( 407 ast.getFirstChild().getText()).replaceAll("\\\\n") 408 + QUOTE; 409 log(ast, MSG_STRING, chopString(logString)); 410 } 411 else { 412 log(ast, MSG_LITERAL, ast.getText()); 413 } 414 } 415 416 /** 417 * Logs a warning for a surrounded TYPECAST when the outer parentheses are 418 * not required by member access, method reference, or another rule's report. 419 * 420 * @param ast the TYPECAST node 421 */ 422 private void logUnnecessaryTypeCast(DetailAST ast) { 423 final DetailAST parent = ast.getParent(); 424 final int parentType = parent.getType(); 425 final boolean isWrappedByOtherRule = 426 parentType == TokenTypes.EXPR 427 || TokenUtil.isOfType(parentType, ASSIGNMENTS); 428 final boolean isReceiverOfMemberAccess = 429 parentType == TokenTypes.DOT 430 || parentType == TokenTypes.INDEX_OP 431 || parentType == TokenTypes.METHOD_REF; 432 if (!isWrappedByOtherRule && !isReceiverOfMemberAccess) { 433 log(ast.getPreviousSibling(), MSG_EXPR); 434 } 435 } 436 437 @Override 438 public void leaveToken(DetailAST ast) { 439 final int type = ast.getType(); 440 final DetailAST parent = ast.getParent(); 441 442 // shouldn't process assign in annotation pairs 443 if (type != TokenTypes.ASSIGN 444 || parent.getType() != TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) { 445 final DetailAST selfOrParentMethodCall = getSelfOrParentMethodCall(ast); 446 if (type == TokenTypes.EXPR) { 447 checkExpression(ast); 448 } 449 else if (TokenUtil.isOfType(type, ASSIGNMENTS)) { 450 assignDepth--; 451 } 452 else if (isSurrounded(selfOrParentMethodCall) && unnecessaryParenAroundOperators(ast)) { 453 log(selfOrParentMethodCall.getPreviousSibling(), MSG_EXPR); 454 } 455 } 456 } 457 458 /** 459 * Get the node itself ot its parent, if it's a method call. 460 * 461 * @param ast AST node 462 * @return node or its parent 463 */ 464 private static DetailAST getSelfOrParentMethodCall(DetailAST ast) { 465 DetailAST selfOrParent = ast; 466 if (ast.getParent().getType() == TokenTypes.METHOD_CALL) { 467 selfOrParent = ast.getParent(); 468 } 469 return selfOrParent; 470 } 471 472 /** 473 * Tests if the given {@code DetailAST} is surrounded by parentheses. 474 * 475 * @param ast the {@code DetailAST} to check if it is surrounded by parentheses. 476 * @return {@code true} if {@code ast} is surrounded by parentheses. 477 */ 478 private static boolean isSurrounded(DetailAST ast) { 479 final DetailAST prev = ast.getPreviousSibling(); 480 return prev != null && prev.getType() == TokenTypes.LPAREN; 481 } 482 483 /** 484 * Tests if the given expression node is surrounded by parentheses. 485 * 486 * @param ast a {@code DetailAST} whose type is 487 * {@code TokenTypes.EXPR}. 488 * @return {@code true} if the expression is surrounded by parentheses. 489 */ 490 private static boolean isExprSurrounded(DetailAST ast) { 491 return ast.getFirstChild().getType() == TokenTypes.LPAREN; 492 } 493 494 /** 495 * Checks whether an expression is surrounded by parentheses. 496 * 497 * @param ast the {@code DetailAST} to check if it is surrounded by parentheses. 498 */ 499 private void checkExpression(DetailAST ast) { 500 // If 'parentToSkip' == 'ast', then we've already logged a 501 // warning about an immediate child node in visitToken, so we don't 502 // need to log another one here. 503 if (parentToSkip != ast && isExprSurrounded(ast)) { 504 if (ast.getParent().getType() == TokenTypes.LITERAL_RETURN) { 505 log(ast, MSG_RETURN); 506 } 507 else if (assignDepth >= 1) { 508 log(ast, MSG_ASSIGN); 509 } 510 else { 511 log(ast, MSG_EXPR); 512 } 513 } 514 } 515 516 /** 517 * Checks if conditional, relational, bitwise binary operator, unary and postfix operators 518 * in expressions are surrounded by unnecessary parentheses. 519 * 520 * @param ast the {@code DetailAST} to check if it is surrounded by unnecessary parentheses. 521 * @return {@code true} if the expression is surrounded by unnecessary parentheses. 522 */ 523 private static boolean unnecessaryParenAroundOperators(DetailAST ast) { 524 final int type = ast.getType(); 525 final boolean isConditionalOrRelational = TokenUtil.isOfType(type, CONDITIONAL_OPERATOR) 526 || TokenUtil.isOfType(type, RELATIONAL_OPERATOR); 527 final boolean isBitwise = TokenUtil.isOfType(type, BITWISE_BINARY_OPERATORS); 528 final boolean hasUnnecessaryParentheses; 529 if (isConditionalOrRelational) { 530 hasUnnecessaryParentheses = checkConditionalOrRelationalOperator(ast); 531 } 532 else if (isBitwise) { 533 hasUnnecessaryParentheses = checkBitwiseBinaryOperator(ast); 534 } 535 else if (TokenUtil.isOfType(type, ARRAY_AND_FIELD_ACCESS)) { 536 hasUnnecessaryParentheses = isNotFirstArgOfTernary(getSelfOrParentMethodCall(ast)); 537 } 538 else { 539 hasUnnecessaryParentheses = TokenUtil.isOfType(type, UNARY_AND_POSTFIX) 540 && isBitWiseBinaryOrConditionalOrRelationalOperator(ast.getParent().getType()); 541 } 542 return hasUnnecessaryParentheses; 543 } 544 545 /** 546 * Check that an expression is not the first argument of a conditional ternary operator. 547 * 548 * @param ast expression 549 * @return whether the expression is not the first argument of a ternary operator 550 */ 551 private static boolean isNotFirstArgOfTernary(DetailAST ast) { 552 return ast.getParent().getType() != TokenTypes.QUESTION 553 || !ast.equals(ast.getParent().getFirstChild().getNextSibling()); 554 } 555 556 /** 557 * Check if conditional or relational operator has unnecessary parentheses. 558 * 559 * @param ast to check if surrounded by unnecessary parentheses 560 * @return true if unnecessary parenthesis 561 */ 562 private static boolean checkConditionalOrRelationalOperator(DetailAST ast) { 563 final int type = ast.getType(); 564 final int parentType = ast.getParent().getType(); 565 final boolean isParentEqualityOperator = 566 TokenUtil.isOfType(parentType, TokenTypes.EQUAL, TokenTypes.NOT_EQUAL); 567 final boolean result; 568 if (type == TokenTypes.LOR) { 569 result = !TokenUtil.isOfType(parentType, TokenTypes.LAND) 570 && !TokenUtil.isOfType(parentType, BITWISE_BINARY_OPERATORS); 571 } 572 else if (type == TokenTypes.LAND) { 573 result = !TokenUtil.isOfType(parentType, BITWISE_BINARY_OPERATORS); 574 } 575 else { 576 result = true; 577 } 578 return result && !isParentEqualityOperator 579 && isBitWiseBinaryOrConditionalOrRelationalOperator(parentType); 580 } 581 582 /** 583 * Check if bitwise binary operator has unnecessary parentheses. 584 * 585 * @param ast to check if surrounded by unnecessary parentheses 586 * @return true if unnecessary parenthesis 587 */ 588 private static boolean checkBitwiseBinaryOperator(DetailAST ast) { 589 final int type = ast.getType(); 590 final int parentType = ast.getParent().getType(); 591 final boolean result; 592 if (type == TokenTypes.BOR) { 593 result = !TokenUtil.isOfType(parentType, TokenTypes.BAND, TokenTypes.BXOR) 594 && !TokenUtil.isOfType(parentType, RELATIONAL_OPERATOR); 595 } 596 else if (type == TokenTypes.BXOR) { 597 result = !TokenUtil.isOfType(parentType, TokenTypes.BAND) 598 && !TokenUtil.isOfType(parentType, RELATIONAL_OPERATOR); 599 } 600 // we deal with bitwise AND here. 601 else { 602 result = !TokenUtil.isOfType(parentType, RELATIONAL_OPERATOR); 603 } 604 return result && isBitWiseBinaryOrConditionalOrRelationalOperator(parentType); 605 } 606 607 /** 608 * Check if token type is bitwise binary or conditional or relational operator. 609 * 610 * @param type Token type to check 611 * @return true if it is bitwise binary or conditional operator 612 */ 613 private static boolean isBitWiseBinaryOrConditionalOrRelationalOperator(int type) { 614 return TokenUtil.isOfType(type, CONDITIONAL_OPERATOR) 615 || TokenUtil.isOfType(type, RELATIONAL_OPERATOR) 616 || TokenUtil.isOfType(type, BITWISE_BINARY_OPERATORS); 617 } 618 619 /** 620 * Tests if the given node has a single parameter, no defined type, and is surrounded 621 * by parentheses. This condition can only be true for lambdas. 622 * 623 * @param ast a {@code DetailAST} node 624 * @return {@code true} if the lambda has a single parameter, no defined type, and is 625 * surrounded by parentheses. 626 */ 627 private static boolean isLambdaSingleParameterSurrounded(DetailAST ast) { 628 final DetailAST firstChild = ast.getFirstChild(); 629 boolean result = false; 630 if (TokenUtil.isOfType(firstChild, TokenTypes.LPAREN)) { 631 final DetailAST parameters = firstChild.getNextSibling(); 632 if (parameters.getChildCount(TokenTypes.PARAMETER_DEF) == 1 633 && !parameters.getFirstChild().findFirstToken(TokenTypes.TYPE).hasChildren()) { 634 result = true; 635 } 636 } 637 return result; 638 } 639 640 /** 641 * Returns the direct LPAREN tokens children to a given QUESTION token which 642 * contain an expression not a literal variable. 643 * 644 * @param questionToken {@code DetailAST} question token to be checked 645 * @return the direct children to the given question token which their types are LPAREN 646 * tokens and not contain a literal inside the parentheses 647 */ 648 private static List<DetailAST> getParenthesesChildrenAroundQuestion(DetailAST questionToken) { 649 final List<DetailAST> surroundedChildren = new ArrayList<>(); 650 DetailAST directChild = questionToken.getFirstChild(); 651 while (directChild != null) { 652 if (directChild.getType() == TokenTypes.LPAREN 653 && !TokenUtil.isOfType(directChild.getNextSibling(), LITERALS)) { 654 surroundedChildren.add(directChild); 655 } 656 directChild = directChild.getNextSibling(); 657 } 658 return Collections.unmodifiableList(surroundedChildren); 659 } 660 661 /** 662 * Returns the specified string chopped to {@code MAX_QUOTED_LENGTH} 663 * plus an ellipsis (...) if the length of the string exceeds {@code MAX_QUOTED_LENGTH}. 664 * 665 * @param value the string to potentially chop. 666 * @return the chopped string if {@code string} is longer than 667 * {@code MAX_QUOTED_LENGTH}; otherwise {@code string}. 668 */ 669 private static String chopString(String value) { 670 String result = value; 671 if (value.length() > MAX_QUOTED_LENGTH) { 672 result = value.substring(0, MAX_QUOTED_LENGTH) + "...\""; 673 } 674 return result; 675 } 676 677}