View Javadoc
1   /*
2    * Copyright (c) 2011-2024 Qulice.com
3    *
4    * All rights reserved.
5    *
6    * Redistribution and use in source and binary forms, with or without
7    * modification, are permitted provided that the following conditions
8    * are met: 1) Redistributions of source code must retain the above
9    * copyright notice, this list of conditions and the following
10   * disclaimer. 2) Redistributions in binary form must reproduce the above
11   * copyright notice, this list of conditions and the following
12   * disclaimer in the documentation and/or other materials provided
13   * with the distribution. 3) Neither the name of the Qulice.com nor
14   * the names of its contributors may be used to endorse or promote
15   * products derived from this software without specific prior written
16   * permission.
17   *
18   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
20   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
21   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
22   * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
29   * OF THE POSSIBILITY OF SUCH DAMAGE.
30   */
31  package com.qulice.pmd.rules;
32  
33  import java.util.Arrays;
34  import java.util.HashMap;
35  import java.util.List;
36  import java.util.Map;
37  import net.sourceforge.pmd.lang.ast.Node;
38  import net.sourceforge.pmd.lang.java.ast.ASTExpression;
39  import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
40  import net.sourceforge.pmd.lang.java.ast.ASTReferenceType;
41  import net.sourceforge.pmd.lang.java.ast.ASTResultType;
42  import net.sourceforge.pmd.lang.java.ast.ASTType;
43  import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
44  import net.sourceforge.pmd.lang.java.ast.JavaNode;
45  import net.sourceforge.pmd.lang.java.symboltable.JavaNameOccurrence;
46  import net.sourceforge.pmd.lang.java.symboltable.MethodNameDeclaration;
47  import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
48  import net.sourceforge.pmd.lang.symboltable.Scope;
49  
50  /**
51   * Rule to prohibit use of String.length() when checking for empty string.
52   * String.isEmpty() should be used instead.
53   * @since 0.18
54   */
55  @SuppressWarnings("deprecation")
56  public final class UseStringIsEmptyRule
57      extends net.sourceforge.pmd.lang.java.rule.AbstractInefficientZeroCheck {
58  
59      @Override
60      public boolean appliesToClassName(final String name) {
61          return net.sourceforge.pmd.util.StringUtil.isSame(
62              name, "String", true, true, true
63          );
64      }
65  
66      @Override
67      public Map<String, List<String>> getComparisonTargets() {
68          final Map<String, List<String>> rules = new HashMap<>();
69          rules.put("<", Arrays.asList("1"));
70          rules.put(">", Arrays.asList("0"));
71          rules.put("==", Arrays.asList("0"));
72          rules.put("!=", Arrays.asList("0"));
73          rules.put(">=", Arrays.asList("0", "1"));
74          rules.put("<=", Arrays.asList("0"));
75          return rules;
76      }
77  
78      @Override
79      public boolean isTargetMethod(final JavaNameOccurrence occ) {
80          final NameOccurrence name = occ.getNameForWhichThisIsAQualifier();
81          return name != null && "length".equals(name.getImage());
82      }
83  
84      @Override
85      public Object visit(
86          final ASTVariableDeclaratorId variable, final Object data
87      ) {
88          final Node node = variable.getTypeNameNode();
89          if (node instanceof ASTReferenceType) {
90              final Class<?> clazz = variable.getType();
91              final String type = variable.getNameDeclaration().getTypeImage();
92              if (clazz != null && !clazz.isArray()
93                  && this.appliesToClassName(type)
94              ) {
95                  final List<NameOccurrence> declarations = variable.getUsages();
96                  this.checkDeclarations(declarations, data);
97              }
98          }
99          variable.childrenAccept(this, data);
100         return data;
101     }
102 
103     @Override
104     public Object visit(
105         final ASTMethodDeclaration declaration, final Object data
106     ) {
107         final ASTResultType result = declaration.getResultType();
108         if (!result.isVoid()) {
109             final ASTType node = (ASTType) result.jjtGetChild(0);
110             final Class<?> clazz = node.getType();
111             final String type = node.getTypeImage();
112             if (clazz != null && !clazz.isArray()
113                 && this.appliesToClassName(type)
114             ) {
115                 final Scope scope = declaration.getScope().getParent();
116                 final MethodNameDeclaration method = new MethodNameDeclaration(
117                     declaration.getMethodDeclarator()
118                 );
119                 final List<NameOccurrence> declarations = scope
120                     .getDeclarations(MethodNameDeclaration.class)
121                     .get(method);
122                 this.checkDeclarations(declarations, data);
123             }
124         }
125         declaration.childrenAccept(this, data);
126         return data;
127     }
128 
129     /**
130      * Checks all uses of a variable or method with a String type.
131      * @param occurrences Variable or method occurrences.
132      * @param data Rule context.
133      */
134     private void checkDeclarations(
135         final Iterable<NameOccurrence> occurrences, final Object data
136     ) {
137         for (final NameOccurrence occurrence : occurrences) {
138             final JavaNameOccurrence jocc = (JavaNameOccurrence) occurrence;
139             if (this.isTargetMethod(jocc)) {
140                 final JavaNode location = jocc.getLocation();
141                 final Node expr = location.getFirstParentOfType(
142                     ASTExpression.class
143                 );
144                 this.checkNodeAndReport(
145                     data, occurrence.getLocation(), expr.jjtGetChild(0)
146                 );
147             }
148         }
149     }
150 }