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