| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
| EqualsAvoidNullCheck |
|
| 3.875;3.875 |
| 1 | //////////////////////////////////////////////////////////////////////////////// | |
| 2 | // checkstyle: Checks Java source code for adherence to a set of rules. | |
| 3 | // Copyright (C) 2001-2014 Oliver Burn | |
| 4 | // | |
| 5 | // This library is free software; you can redistribute it and/or | |
| 6 | // modify it under the terms of the GNU Lesser General Public | |
| 7 | // License as published by the Free Software Foundation; either | |
| 8 | // version 2.1 of the License, or (at your option) any later version. | |
| 9 | // | |
| 10 | // This library is distributed in the hope that it will be useful, | |
| 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 13 | // Lesser General Public License for more details. | |
| 14 | // | |
| 15 | // You should have received a copy of the GNU Lesser General Public | |
| 16 | // License along with this library; if not, write to the Free Software | |
| 17 | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
| 18 | //////////////////////////////////////////////////////////////////////////////// | |
| 19 | ||
| 20 | package com.puppycrawl.tools.checkstyle.checks.coding; | |
| 21 | ||
| 22 | import antlr.collections.AST; | |
| 23 | import com.puppycrawl.tools.checkstyle.api.Check; | |
| 24 | import com.puppycrawl.tools.checkstyle.api.DetailAST; | |
| 25 | import com.puppycrawl.tools.checkstyle.api.TokenTypes; | |
| 26 | ||
| 27 | /** | |
| 28 | * <p> | |
| 29 | * Checks that any combination of String literals with optional | |
| 30 | * assignment is on the left side of an equals() comparison. | |
| 31 | * </p> | |
| 32 | * | |
| 33 | * <p> | |
| 34 | * Rationale: Calling the equals() method on String literals | |
| 35 | * will avoid a potential NullPointerException. Also, it is | |
| 36 | * pretty common to see null check right before equals comparisons | |
| 37 | * which is not necessary in the below example. | |
| 38 | * | |
| 39 | * For example: | |
| 40 | * | |
| 41 | * <pre> | |
| 42 | * <code> | |
| 43 | * String nullString = null; | |
| 44 | * nullString.equals("My_Sweet_String"); | |
| 45 | * </code> | |
| 46 | * </pre> | |
| 47 | * should be refactored to | |
| 48 | * | |
| 49 | * <pre> | |
| 50 | * <code> | |
| 51 | * String nullString = null; | |
| 52 | * "My_Sweet_String".equals(nullString); | |
| 53 | * </code> | |
| 54 | * </pre> | |
| 55 | * </p> | |
| 56 | * | |
| 57 | * <p> | |
| 58 | * Limitations: If the equals method is overridden or | |
| 59 | * a covariant equals method is defined and the implementation | |
| 60 | * is incorrect (where s.equals(t) does not return the same result | |
| 61 | * as t.equals(s)) then rearranging the called on object and | |
| 62 | * parameter may have unexpected results | |
| 63 | * | |
| 64 | * <br/> | |
| 65 | * | |
| 66 | * Java's Autoboxing feature has an affect | |
| 67 | * on how this check is implemented. Pre Java 5 all IDENT + IDENT | |
| 68 | * object concatenations would not cause a NullPointerException even | |
| 69 | * if null. Those situations could have been included in this check. | |
| 70 | * They would simply act as if they surrounded by String.valueOf() | |
| 71 | * which would concatenate the String null. | |
| 72 | * </p> | |
| 73 | * <p> | |
| 74 | * The following example will cause a | |
| 75 | * NullPointerException as a result of what autoboxing does. | |
| 76 | * <pre> | |
| 77 | * Integer i = null, j = null; | |
| 78 | * String number = "5" | |
| 79 | * number.equals(i + j); | |
| 80 | * </pre> | |
| 81 | * </p> | |
| 82 | * | |
| 83 | * Since, it is difficult to determine what kind of Object is being | |
| 84 | * concatenated all ident concatenation is considered unsafe. | |
| 85 | * | |
| 86 | * @author Travis Schneeberger | |
| 87 | * version 1.0 | |
| 88 | */ | |
| 89 | 2 | public class EqualsAvoidNullCheck extends Check |
| 90 | { | |
| 91 | /** Whether to process equalsIgnoreCase() invocations. */ | |
| 92 | private boolean mIgnoreEqualsIgnoreCase; | |
| 93 | ||
| 94 | @Override | |
| 95 | public int[] getDefaultTokens() | |
| 96 | { | |
| 97 | 2 | return new int[] {TokenTypes.METHOD_CALL}; |
| 98 | } | |
| 99 | ||
| 100 | @Override | |
| 101 | public void visitToken(final DetailAST aMethodCall) | |
| 102 | { | |
| 103 | 132 | final DetailAST dot = aMethodCall.getFirstChild(); |
| 104 | 132 | if (dot.getType() != TokenTypes.DOT) { |
| 105 | 12 | return; |
| 106 | } | |
| 107 | ||
| 108 | 120 | 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 | 120 | if ((objCalledOn.getType() == TokenTypes.STRING_LITERAL) |
| 116 | || (objCalledOn.getType() == TokenTypes.LITERAL_NEW) | |
| 117 | || (objCalledOn.getType() == TokenTypes.DOT)) | |
| 118 | { | |
| 119 | 20 | return; |
| 120 | } | |
| 121 | ||
| 122 | 100 | final DetailAST method = objCalledOn.getNextSibling(); |
| 123 | 100 | final DetailAST expr = dot.getNextSibling().getFirstChild(); |
| 124 | ||
| 125 | 100 | if ("equals".equals(method.getText()) |
| 126 | && containsOneArg(expr) && containsAllSafeTokens(expr)) | |
| 127 | { | |
| 128 | 24 | log(aMethodCall.getLineNo(), aMethodCall.getColumnNo(), |
| 129 | "equals.avoid.null"); | |
| 130 | } | |
| 131 | ||
| 132 | 100 | if (!mIgnoreEqualsIgnoreCase |
| 133 | && "equalsIgnoreCase".equals(method.getText()) | |
| 134 | && containsOneArg(expr) && containsAllSafeTokens(expr)) | |
| 135 | { | |
| 136 | 12 | log(aMethodCall.getLineNo(), aMethodCall.getColumnNo(), |
| 137 | "equalsIgnoreCase.avoid.null"); | |
| 138 | } | |
| 139 | 100 | } |
| 140 | ||
| 141 | /** | |
| 142 | * Checks if a method contains no arguments | |
| 143 | * starting at with the argument expression. | |
| 144 | * | |
| 145 | * @param aExpr the argument expression | |
| 146 | * @return true if the method contains no args, false if not | |
| 147 | */ | |
| 148 | private boolean containsNoArgs(final AST aExpr) | |
| 149 | { | |
| 150 | 58 | return (aExpr == null); |
| 151 | } | |
| 152 | ||
| 153 | /** | |
| 154 | * Checks if a method contains multiple arguments | |
| 155 | * starting at with the argument expression. | |
| 156 | * | |
| 157 | * @param aExpr the argument expression | |
| 158 | * @return true if the method contains multiple args, false if not | |
| 159 | */ | |
| 160 | private boolean containsMultiArgs(final AST aExpr) | |
| 161 | { | |
| 162 | 58 | final AST comma = aExpr.getNextSibling(); |
| 163 | 58 | 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 aExpr the argument expression | |
| 171 | * @return true if the method contains a single arg, false if not | |
| 172 | */ | |
| 173 | private boolean containsOneArg(final AST aExpr) | |
| 174 | { | |
| 175 | 58 | return !containsNoArgs(aExpr) && !containsMultiArgs(aExpr); |
| 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 aExpr the argument expression | |
| 190 | * @return - true if any child matches the set of tokens, false if not | |
| 191 | */ | |
| 192 | private boolean containsAllSafeTokens(final DetailAST aExpr) | |
| 193 | { | |
| 194 | 58 | DetailAST arg = aExpr.getFirstChild(); |
| 195 | ||
| 196 | 58 | if (arg.branchContains(TokenTypes.METHOD_CALL)) { |
| 197 | 4 | return false; |
| 198 | } | |
| 199 | 54 | 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 | 54 | if ((arg).branchContains(TokenTypes.PLUS_ASSIGN) |
| 211 | || (arg).branchContains(TokenTypes.IDENT)) | |
| 212 | { | |
| 213 | 18 | return false; |
| 214 | } | |
| 215 | ||
| 216 | //must be just String literals if got here | |
| 217 | 36 | return true; |
| 218 | } | |
| 219 | ||
| 220 | /** | |
| 221 | * Skips over an inner assign portion of an argument expression. | |
| 222 | * @param aCurrentAST current token in the argument expression | |
| 223 | * @return the next relevant token | |
| 224 | */ | |
| 225 | private DetailAST skipVariableAssign(final DetailAST aCurrentAST) | |
| 226 | { | |
| 227 | 54 | if ((aCurrentAST.getType() == TokenTypes.ASSIGN) |
| 228 | && (aCurrentAST.getFirstChild().getType() == TokenTypes.IDENT)) | |
| 229 | { | |
| 230 | 6 | return aCurrentAST.getFirstChild().getNextSibling(); |
| 231 | } | |
| 232 | 48 | return aCurrentAST; |
| 233 | } | |
| 234 | ||
| 235 | /** | |
| 236 | * Whether to ignore checking {@code String.equalsIgnoreCase(String)}. | |
| 237 | * @param aNewValue whether to ignore checking | |
| 238 | * {@code String.equalsIgnoreCase(String)}. | |
| 239 | */ | |
| 240 | public void setIgnoreEqualsIgnoreCase(boolean aNewValue) | |
| 241 | { | |
| 242 | 1 | mIgnoreEqualsIgnoreCase = aNewValue; |
| 243 | 1 | } |
| 244 | } |