001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2014 Oliver Burn 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 antlr.collections.AST; 023import com.puppycrawl.tools.checkstyle.api.Check; 024import com.puppycrawl.tools.checkstyle.api.DetailAST; 025import com.puppycrawl.tools.checkstyle.api.TokenTypes; 026 027/** 028 * <p> 029 * Checks that any combination of String literals with optional 030 * assignment is on the left side of an equals() comparison. 031 * </p> 032 * 033 * <p> 034 * Rationale: Calling the equals() method on String literals 035 * will avoid a potential NullPointerException. Also, it is 036 * pretty common to see null check right before equals comparisons 037 * which is not necessary in the below example. 038 * 039 * For example: 040 * 041 * <pre> 042 * <code> 043 * String nullString = null; 044 * nullString.equals("My_Sweet_String"); 045 * </code> 046 * </pre> 047 * should be refactored to 048 * 049 * <pre> 050 * <code> 051 * String nullString = null; 052 * "My_Sweet_String".equals(nullString); 053 * </code> 054 * </pre> 055 * 056 * 057 * <p> 058 * Limitations: If the equals method is overridden or 059 * a covariant equals method is defined and the implementation 060 * is incorrect (where s.equals(t) does not return the same result 061 * as t.equals(s)) then rearranging the called on object and 062 * parameter may have unexpected results 063 * 064 * <br> 065 * 066 * Java's Autoboxing feature has an affect 067 * on how this check is implemented. Pre Java 5 all IDENT + IDENT 068 * object concatenations would not cause a NullPointerException even 069 * if null. Those situations could have been included in this check. 070 * They would simply act as if they surrounded by String.valueOf() 071 * which would concatenate the String null. 072 * 073 * <p> 074 * The following example will cause a 075 * NullPointerException as a result of what autoboxing does. 076 * <pre> 077 * Integer i = null, j = null; 078 * String number = "5" 079 * number.equals(i + j); 080 * </pre> 081 * 082 * 083 * Since, it is difficult to determine what kind of Object is being 084 * concatenated all ident concatenation is considered unsafe. 085 * 086 * @author Travis Schneeberger 087 * version 1.0 088 */ 089public class EqualsAvoidNullCheck extends Check 090{ 091 /** Whether to process equalsIgnoreCase() invocations. */ 092 private boolean ignoreEqualsIgnoreCase; 093 094 @Override 095 public int[] getDefaultTokens() 096 { 097 return new int[] {TokenTypes.METHOD_CALL}; 098 } 099 100 @Override 101 public void visitToken(final DetailAST methodCall) 102 { 103 final DetailAST dot = methodCall.getFirstChild(); 104 if (dot.getType() != TokenTypes.DOT) { 105 return; 106 } 107 108 final DetailAST objCalledOn = dot.getFirstChild(); 109 110 //checks for calling equals on String literal and 111 //anon object which cannot be null 112 //Also, checks if calling using strange inner class 113 //syntax outter.inner.equals(otherObj) by looking 114 //for the dot operator which cannot be improved 115 if ((objCalledOn.getType() == TokenTypes.STRING_LITERAL) 116 || (objCalledOn.getType() == TokenTypes.LITERAL_NEW) 117 || (objCalledOn.getType() == TokenTypes.DOT)) 118 { 119 return; 120 } 121 122 final DetailAST method = objCalledOn.getNextSibling(); 123 final DetailAST expr = dot.getNextSibling().getFirstChild(); 124 125 if ("equals".equals(method.getText()) 126 && containsOneArg(expr) && containsAllSafeTokens(expr)) 127 { 128 log(methodCall.getLineNo(), methodCall.getColumnNo(), 129 "equals.avoid.null"); 130 } 131 132 if (!ignoreEqualsIgnoreCase 133 && "equalsIgnoreCase".equals(method.getText()) 134 && containsOneArg(expr) && containsAllSafeTokens(expr)) 135 { 136 log(methodCall.getLineNo(), methodCall.getColumnNo(), 137 "equalsIgnoreCase.avoid.null"); 138 } 139 } 140 141 /** 142 * Checks if a method contains no arguments 143 * starting at with the argument expression. 144 * 145 * @param expr the argument expression 146 * @return true if the method contains no args, false if not 147 */ 148 private boolean containsNoArgs(final AST expr) 149 { 150 return (expr == null); 151 } 152 153 /** 154 * Checks if a method contains multiple arguments 155 * starting at with the argument expression. 156 * 157 * @param expr the argument expression 158 * @return true if the method contains multiple args, false if not 159 */ 160 private boolean containsMultiArgs(final AST expr) 161 { 162 final AST comma = expr.getNextSibling(); 163 return (comma != null) && (comma.getType() == TokenTypes.COMMA); 164 } 165 166 /** 167 * Checks if a method contains a single argument 168 * starting at with the argument expression. 169 * 170 * @param expr the argument expression 171 * @return true if the method contains a single arg, false if not 172 */ 173 private boolean containsOneArg(final AST expr) 174 { 175 return !containsNoArgs(expr) && !containsMultiArgs(expr); 176 } 177 178 /** 179 * <p> 180 * Looks for all "safe" Token combinations in the argument 181 * expression branch. 182 * </p> 183 * 184 * <p> 185 * See class documentation for details on autoboxing's affect 186 * on this method implementation. 187 * </p> 188 * 189 * @param expr the argument expression 190 * @return - true if any child matches the set of tokens, false if not 191 */ 192 private boolean containsAllSafeTokens(final DetailAST expr) 193 { 194 DetailAST arg = expr.getFirstChild(); 195 196 if (arg.branchContains(TokenTypes.METHOD_CALL)) { 197 return false; 198 } 199 arg = skipVariableAssign(arg); 200 201 //Plus assignment can have ill affects 202 //do not want to recommend moving expression 203 //See example: 204 //String s = "SweetString"; 205 //s.equals(s += "SweetString"); //false 206 //s = "SweetString"; 207 //(s += "SweetString").equals(s); //true 208 //arg = skipVariablePlusAssign(arg); 209 210 if ((arg).branchContains(TokenTypes.PLUS_ASSIGN) 211 || (arg).branchContains(TokenTypes.IDENT)) 212 { 213 return false; 214 } 215 216 //must be just String literals if got here 217 return true; 218 } 219 220 /** 221 * Skips over an inner assign portion of an argument expression. 222 * @param currentAST current token in the argument expression 223 * @return the next relevant token 224 */ 225 private DetailAST skipVariableAssign(final DetailAST currentAST) 226 { 227 if ((currentAST.getType() == TokenTypes.ASSIGN) 228 && (currentAST.getFirstChild().getType() == TokenTypes.IDENT)) 229 { 230 return currentAST.getFirstChild().getNextSibling(); 231 } 232 return currentAST; 233 } 234 235 /** 236 * Whether to ignore checking {@code String.equalsIgnoreCase(String)}. 237 * @param newValue whether to ignore checking 238 * {@code String.equalsIgnoreCase(String)}. 239 */ 240 public void setIgnoreEqualsIgnoreCase(boolean newValue) 241 { 242 ignoreEqualsIgnoreCase = newValue; 243 } 244}