View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2026 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   package com.qulice.pmd.rules;
6   
7   import net.sourceforge.pmd.lang.java.ast.ASTExpression;
8   import net.sourceforge.pmd.lang.java.ast.ASTInfixExpression;
9   import net.sourceforge.pmd.lang.java.ast.ASTMethodCall;
10  import net.sourceforge.pmd.lang.java.ast.ASTNumericLiteral;
11  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule;
12  import net.sourceforge.pmd.lang.java.types.JTypeMirror;
13  
14  /**
15   * Rule to prohibit use of String.length() when checking for empty string.
16   * String.isEmpty() should be used instead.
17   * @since 0.18
18   */
19  public final class UseStringIsEmptyRule extends AbstractJavaRulechainRule {
20  
21      public UseStringIsEmptyRule() {
22          super(ASTInfixExpression.class);
23      }
24  
25      @Override
26      public Object visit(final ASTInfixExpression expr, final Object data) {
27          if (isComparison(expr)
28              && (matchesLengthCheck(expr.getLeftOperand(), expr.getRightOperand())
29                  || matchesLengthCheck(expr.getRightOperand(), expr.getLeftOperand()))
30          ) {
31              asCtx(data).addViolation(expr);
32          }
33          return data;
34      }
35  
36      private static boolean isComparison(final ASTInfixExpression expr) {
37          final boolean result;
38          switch (expr.getOperator()) {
39              case EQ:
40              case NE:
41              case GT:
42              case LT:
43              case GE:
44              case LE:
45                  result = true;
46                  break;
47              default:
48                  result = false;
49                  break;
50          }
51          return result;
52      }
53  
54      /**
55       * Checks if length is length() or literal is 0 or 1.
56       * @param length The method
57       * @param literal The number
58       * @return True if matches, false otherwise
59       * @checkstyle BooleanExpressionComplexityCheck (20 lines)
60       */
61      private static boolean matchesLengthCheck(
62          final ASTExpression length,
63          final ASTExpression literal
64      ) {
65          boolean result = false;
66          if (length != null && literal != null && isZeroOrOneLiteral(literal)
67              && length instanceof ASTMethodCall) {
68              final ASTMethodCall call = (ASTMethodCall) length;
69              result = "length".equals(call.getMethodName())
70                  && call.getArguments().isEmpty()
71                  && call.getQualifier() != null
72                  && isStringExpression(call.getQualifier());
73          }
74          return result;
75      }
76  
77      private static boolean isZeroOrOneLiteral(final ASTExpression expr) {
78          boolean matches = false;
79          if (expr instanceof ASTNumericLiteral) {
80              final ASTNumericLiteral lit = (ASTNumericLiteral) expr;
81              final String image = lit.getImage();
82              matches = "0".equals(image) || "1".equals(image);
83          }
84          return matches;
85      }
86  
87      /**
88       * Checks if the expression is of {@link String} type.
89       * @param expr The expression to check
90       * @return True if expression has {@link String} type
91       */
92      private static boolean isStringExpression(final ASTExpression expr) {
93          final JTypeMirror type = expr.getTypeMirror();
94          return type.isClassOrInterface()
95              && String.class.getName().equals(type.toString());
96      }
97  }