001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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.whitespace; 021 022import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 023import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 024import com.puppycrawl.tools.checkstyle.api.DetailAST; 025import com.puppycrawl.tools.checkstyle.api.TokenTypes; 026import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 027 028/** 029 * <p> 030 * Checks that the whitespace around the Generic tokens (angle brackets) 031 * "<" and ">" are correct to the <i>typical</i> convention. 032 * The convention is not configurable. 033 * </p> 034 * <p> 035 * Left angle bracket ("<"): 036 * </p> 037 * <ul> 038 * <li> should be preceded with whitespace only 039 * in generic methods definitions.</li> 040 * <li> should not be preceded with whitespace 041 * when it is precede method name or constructor.</li> 042 * <li> should not be preceded with whitespace when following type name.</li> 043 * <li> should not be followed with whitespace in all cases.</li> 044 * </ul> 045 * <p> 046 * Right angle bracket (">"): 047 * </p> 048 * <ul> 049 * <li> should not be preceded with whitespace in all cases.</li> 050 * <li> should be followed with whitespace in almost all cases, 051 * except diamond operators and when preceding method name or constructor.</li></ul> 052 * <p> 053 * To configure the check: 054 * </p> 055 * <pre> 056 * <module name="GenericWhitespace"/> 057 * </pre> 058 * <p> 059 * Examples with correct spacing: 060 * </p> 061 * <pre> 062 * // Generic methods definitions 063 * public void <K, V extends Number> boolean foo(K, V) {} 064 * // Generic type definition 065 * class name<T1, T2, ..., Tn> {} 066 * // Generic type reference 067 * OrderedPair<String, Box<Integer>> p; 068 * // Generic preceded method name 069 * boolean same = Util.<Integer, String>compare(p1, p2); 070 * // Diamond operator 071 * Pair<Integer, String> p1 = new Pair<>(1, "apple"); 072 * // Method reference 073 * List<T> list = ImmutableList.Builder<T>::new; 074 * // Method reference 075 * sort(list, Comparable::<String>compareTo); 076 * // Constructor call 077 * MyClass obj = new <String>MyClass(); 078 * </pre> 079 * <p> 080 * Examples with incorrect spacing: 081 * </p> 082 * <pre> 083 * List< String> l; // violation, "<" followed by whitespace 084 * Box b = Box. <String>of("foo"); // violation, "<" preceded with whitespace 085 * public<T> void foo() {} // violation, "<" not preceded with whitespace 086 * 087 * List a = new ArrayList<> (); // violation, ">" followed by whitespace 088 * Map<Integer, String>m; // violation, ">" not followed by whitespace 089 * Pair<Integer, Integer > p; // violation, ">" preceded with whitespace 090 * </pre> 091 * 092 * @since 5.0 093 */ 094@FileStatefulCheck 095public class GenericWhitespaceCheck extends AbstractCheck { 096 097 /** 098 * A key is pointing to the warning message text in "messages.properties" 099 * file. 100 */ 101 public static final String MSG_WS_PRECEDED = "ws.preceded"; 102 103 /** 104 * A key is pointing to the warning message text in "messages.properties" 105 * file. 106 */ 107 public static final String MSG_WS_FOLLOWED = "ws.followed"; 108 109 /** 110 * A key is pointing to the warning message text in "messages.properties" 111 * file. 112 */ 113 public static final String MSG_WS_NOT_PRECEDED = "ws.notPreceded"; 114 115 /** 116 * A key is pointing to the warning message text in "messages.properties" 117 * file. 118 */ 119 public static final String MSG_WS_ILLEGAL_FOLLOW = "ws.illegalFollow"; 120 121 /** Open angle bracket literal. */ 122 private static final String OPEN_ANGLE_BRACKET = "<"; 123 124 /** Close angle bracket literal. */ 125 private static final String CLOSE_ANGLE_BRACKET = ">"; 126 127 /** Used to count the depth of a Generic expression. */ 128 private int depth; 129 130 @Override 131 public int[] getDefaultTokens() { 132 return getRequiredTokens(); 133 } 134 135 @Override 136 public int[] getAcceptableTokens() { 137 return getRequiredTokens(); 138 } 139 140 @Override 141 public int[] getRequiredTokens() { 142 return new int[] {TokenTypes.GENERIC_START, TokenTypes.GENERIC_END}; 143 } 144 145 @Override 146 public void beginTree(DetailAST rootAST) { 147 // Reset for each tree, just increase there are violations in preceding 148 // trees. 149 depth = 0; 150 } 151 152 @Override 153 public void visitToken(DetailAST ast) { 154 switch (ast.getType()) { 155 case TokenTypes.GENERIC_START: 156 processStart(ast); 157 depth++; 158 break; 159 case TokenTypes.GENERIC_END: 160 processEnd(ast); 161 depth--; 162 break; 163 default: 164 throw new IllegalArgumentException("Unknown type " + ast); 165 } 166 } 167 168 /** 169 * Checks the token for the end of Generics. 170 * 171 * @param ast the token to check 172 */ 173 private void processEnd(DetailAST ast) { 174 final String line = getLine(ast.getLineNo() - 1); 175 final int before = ast.getColumnNo() - 1; 176 final int after = ast.getColumnNo() + 1; 177 178 if (before >= 0 && Character.isWhitespace(line.charAt(before)) 179 && !containsWhitespaceBefore(before, line)) { 180 log(ast, MSG_WS_PRECEDED, CLOSE_ANGLE_BRACKET); 181 } 182 183 if (after < line.length()) { 184 // Check if the last Generic, in which case must be a whitespace 185 // or a '(),[.'. 186 if (depth == 1) { 187 processSingleGeneric(ast, line, after); 188 } 189 else { 190 processNestedGenerics(ast, line, after); 191 } 192 } 193 } 194 195 /** 196 * Process Nested generics. 197 * 198 * @param ast token 199 * @param line line content 200 * @param after position after 201 */ 202 private void processNestedGenerics(DetailAST ast, String line, int after) { 203 // In a nested Generic type, so can only be a '>' or ',' or '&' 204 205 // In case of several extends definitions: 206 // 207 // class IntEnumValueType<E extends Enum<E> & IntEnum> 208 // ^ 209 // should be whitespace if followed by & -+ 210 // 211 final int indexOfAmp = line.indexOf('&', after); 212 if (indexOfAmp >= 1 213 && containsWhitespaceBetween(after, indexOfAmp, line)) { 214 if (indexOfAmp - after == 0) { 215 log(ast, MSG_WS_NOT_PRECEDED, "&"); 216 } 217 else if (indexOfAmp - after != 1) { 218 log(ast, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET); 219 } 220 } 221 else if (line.charAt(after) == ' ') { 222 log(ast, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET); 223 } 224 } 225 226 /** 227 * Process Single-generic. 228 * 229 * @param ast token 230 * @param line line content 231 * @param after position after 232 */ 233 private void processSingleGeneric(DetailAST ast, String line, int after) { 234 final char charAfter = line.charAt(after); 235 if (isGenericBeforeMethod(ast) || isGenericBeforeCtor(ast)) { 236 if (Character.isWhitespace(charAfter)) { 237 log(ast, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET); 238 } 239 } 240 else if (!isCharacterValidAfterGenericEnd(charAfter)) { 241 log(ast, MSG_WS_ILLEGAL_FOLLOW, CLOSE_ANGLE_BRACKET); 242 } 243 } 244 245 /** 246 * Checks if generic is before constructor invocation. 247 * 248 * @param ast ast 249 * @return true if generic before a constructor invocation 250 */ 251 private static boolean isGenericBeforeCtor(DetailAST ast) { 252 final DetailAST parent = ast.getParent(); 253 return parent.getParent().getType() == TokenTypes.LITERAL_NEW 254 && (parent.getNextSibling().getType() == TokenTypes.IDENT 255 || parent.getNextSibling().getType() == TokenTypes.DOT); 256 } 257 258 /** 259 * Is generic before method reference. 260 * 261 * @param ast ast 262 * @return true if generic before a method ref 263 */ 264 private static boolean isGenericBeforeMethod(DetailAST ast) { 265 return ast.getParent().getParent().getType() == TokenTypes.DOT 266 && ast.getParent().getParent().getParent().getType() == TokenTypes.METHOD_CALL 267 || isAfterMethodReference(ast); 268 } 269 270 /** 271 * Checks if current generic end ('>') is located after 272 * {@link TokenTypes#METHOD_REF method reference operator}. 273 * 274 * @param genericEnd {@link TokenTypes#GENERIC_END} 275 * @return true if '>' follows after method reference. 276 */ 277 private static boolean isAfterMethodReference(DetailAST genericEnd) { 278 return genericEnd.getParent().getParent().getType() == TokenTypes.METHOD_REF; 279 } 280 281 /** 282 * Checks the token for the start of Generics. 283 * 284 * @param ast the token to check 285 */ 286 private void processStart(DetailAST ast) { 287 final String line = getLine(ast.getLineNo() - 1); 288 final int before = ast.getColumnNo() - 1; 289 final int after = ast.getColumnNo() + 1; 290 291 // Need to handle two cases as in: 292 // 293 // public static <T> Callable<T> callable(Runnable task, T result) 294 // ^ ^ 295 // ws reqd ---+ +--- whitespace NOT required 296 // 297 if (before >= 0) { 298 // Detect if the first case 299 final DetailAST parent = ast.getParent(); 300 final DetailAST grandparent = parent.getParent(); 301 if (grandparent.getType() == TokenTypes.CTOR_DEF 302 || grandparent.getType() == TokenTypes.METHOD_DEF 303 || isGenericBeforeCtor(ast)) { 304 // Require whitespace 305 if (!Character.isWhitespace(line.charAt(before))) { 306 log(ast, MSG_WS_NOT_PRECEDED, OPEN_ANGLE_BRACKET); 307 } 308 } 309 // Whitespace not required 310 else if (Character.isWhitespace(line.charAt(before)) 311 && !containsWhitespaceBefore(before, line)) { 312 log(ast, MSG_WS_PRECEDED, OPEN_ANGLE_BRACKET); 313 } 314 } 315 316 if (after < line.length() 317 && Character.isWhitespace(line.charAt(after))) { 318 log(ast, MSG_WS_FOLLOWED, OPEN_ANGLE_BRACKET); 319 } 320 } 321 322 /** 323 * Returns whether the specified string contains only whitespace between 324 * specified indices. 325 * 326 * @param fromIndex the index to start the search from. Inclusive 327 * @param toIndex the index to finish the search. Exclusive 328 * @param line the line to check 329 * @return whether there are only whitespaces (or nothing) 330 */ 331 private static boolean containsWhitespaceBetween(int fromIndex, int toIndex, String line) { 332 boolean result = true; 333 for (int i = fromIndex; i < toIndex; i++) { 334 if (!Character.isWhitespace(line.charAt(i))) { 335 result = false; 336 break; 337 } 338 } 339 return result; 340 } 341 342 /** 343 * Returns whether the specified string contains only whitespace up to specified index. 344 * 345 * @param before the index to start the search from. Inclusive 346 * @param line the index to finish the search. Exclusive 347 * @return {@code true} if there are only whitespaces, 348 * false if there is nothing before or some other characters 349 */ 350 private static boolean containsWhitespaceBefore(int before, String line) { 351 return before != 0 && CommonUtil.hasWhitespaceBefore(before, line); 352 } 353 354 /** 355 * Checks whether given character is valid to be right after generic ends. 356 * 357 * @param charAfter character to check 358 * @return checks if given character is valid 359 */ 360 private static boolean isCharacterValidAfterGenericEnd(char charAfter) { 361 return charAfter == '(' || charAfter == ')' 362 || charAfter == ',' || charAfter == '[' 363 || charAfter == '.' || charAfter == ':' 364 || charAfter == ';' 365 || Character.isWhitespace(charAfter); 366 } 367 368}