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 * <p> 092 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 093 * </p> 094 * <p> 095 * Violation Message Keys: 096 * </p> 097 * <ul> 098 * <li> 099 * {@code ws.followed} 100 * </li> 101 * <li> 102 * {@code ws.illegalFollow} 103 * </li> 104 * <li> 105 * {@code ws.notPreceded} 106 * </li> 107 * <li> 108 * {@code ws.preceded} 109 * </li> 110 * </ul> 111 * 112 * @since 5.0 113 */ 114@FileStatefulCheck 115public class GenericWhitespaceCheck extends AbstractCheck { 116 117 /** 118 * A key is pointing to the warning message text in "messages.properties" 119 * file. 120 */ 121 public static final String MSG_WS_PRECEDED = "ws.preceded"; 122 123 /** 124 * A key is pointing to the warning message text in "messages.properties" 125 * file. 126 */ 127 public static final String MSG_WS_FOLLOWED = "ws.followed"; 128 129 /** 130 * A key is pointing to the warning message text in "messages.properties" 131 * file. 132 */ 133 public static final String MSG_WS_NOT_PRECEDED = "ws.notPreceded"; 134 135 /** 136 * A key is pointing to the warning message text in "messages.properties" 137 * file. 138 */ 139 public static final String MSG_WS_ILLEGAL_FOLLOW = "ws.illegalFollow"; 140 141 /** Open angle bracket literal. */ 142 private static final String OPEN_ANGLE_BRACKET = "<"; 143 144 /** Close angle bracket literal. */ 145 private static final String CLOSE_ANGLE_BRACKET = ">"; 146 147 /** Used to count the depth of a Generic expression. */ 148 private int depth; 149 150 @Override 151 public int[] getDefaultTokens() { 152 return getRequiredTokens(); 153 } 154 155 @Override 156 public int[] getAcceptableTokens() { 157 return getRequiredTokens(); 158 } 159 160 @Override 161 public int[] getRequiredTokens() { 162 return new int[] {TokenTypes.GENERIC_START, TokenTypes.GENERIC_END}; 163 } 164 165 @Override 166 public void beginTree(DetailAST rootAST) { 167 // Reset for each tree, just increase there are violations in preceding 168 // trees. 169 depth = 0; 170 } 171 172 @Override 173 public void visitToken(DetailAST ast) { 174 switch (ast.getType()) { 175 case TokenTypes.GENERIC_START: 176 processStart(ast); 177 depth++; 178 break; 179 case TokenTypes.GENERIC_END: 180 processEnd(ast); 181 depth--; 182 break; 183 default: 184 throw new IllegalArgumentException("Unknown type " + ast); 185 } 186 } 187 188 /** 189 * Checks the token for the end of Generics. 190 * 191 * @param ast the token to check 192 */ 193 private void processEnd(DetailAST ast) { 194 final String line = getLine(ast.getLineNo() - 1); 195 final int before = ast.getColumnNo() - 1; 196 final int after = ast.getColumnNo() + 1; 197 198 if (before >= 0 && Character.isWhitespace(line.charAt(before)) 199 && !containsWhitespaceBefore(before, line)) { 200 log(ast, MSG_WS_PRECEDED, CLOSE_ANGLE_BRACKET); 201 } 202 203 if (after < line.length()) { 204 // Check if the last Generic, in which case must be a whitespace 205 // or a '(),[.'. 206 if (depth == 1) { 207 processSingleGeneric(ast, line, after); 208 } 209 else { 210 processNestedGenerics(ast, line, after); 211 } 212 } 213 } 214 215 /** 216 * Process Nested generics. 217 * 218 * @param ast token 219 * @param line line content 220 * @param after position after 221 */ 222 private void processNestedGenerics(DetailAST ast, String line, int after) { 223 // In a nested Generic type, so can only be a '>' or ',' or '&' 224 225 // In case of several extends definitions: 226 // 227 // class IntEnumValueType<E extends Enum<E> & IntEnum> 228 // ^ 229 // should be whitespace if followed by & -+ 230 // 231 final int indexOfAmp = line.indexOf('&', after); 232 if (indexOfAmp >= 1 233 && containsWhitespaceBetween(after, indexOfAmp, line)) { 234 if (indexOfAmp - after == 0) { 235 log(ast, MSG_WS_NOT_PRECEDED, "&"); 236 } 237 else if (indexOfAmp - after != 1) { 238 log(ast, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET); 239 } 240 } 241 else if (line.charAt(after) == ' ') { 242 log(ast, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET); 243 } 244 } 245 246 /** 247 * Process Single-generic. 248 * 249 * @param ast token 250 * @param line line content 251 * @param after position after 252 */ 253 private void processSingleGeneric(DetailAST ast, String line, int after) { 254 final char charAfter = line.charAt(after); 255 if (isGenericBeforeMethod(ast) || isGenericBeforeCtor(ast)) { 256 if (Character.isWhitespace(charAfter)) { 257 log(ast, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET); 258 } 259 } 260 else if (!isCharacterValidAfterGenericEnd(charAfter)) { 261 log(ast, MSG_WS_ILLEGAL_FOLLOW, CLOSE_ANGLE_BRACKET); 262 } 263 } 264 265 /** 266 * Checks if generic is before constructor invocation. 267 * 268 * @param ast ast 269 * @return true if generic before a constructor invocation 270 */ 271 private static boolean isGenericBeforeCtor(DetailAST ast) { 272 final DetailAST parent = ast.getParent(); 273 return parent.getParent().getType() == TokenTypes.LITERAL_NEW 274 && (parent.getNextSibling().getType() == TokenTypes.IDENT 275 || parent.getNextSibling().getType() == TokenTypes.DOT); 276 } 277 278 /** 279 * Is generic before method reference. 280 * 281 * @param ast ast 282 * @return true if generic before a method ref 283 */ 284 private static boolean isGenericBeforeMethod(DetailAST ast) { 285 return ast.getParent().getParent().getType() == TokenTypes.DOT 286 && ast.getParent().getParent().getParent().getType() == TokenTypes.METHOD_CALL 287 || isAfterMethodReference(ast); 288 } 289 290 /** 291 * Checks if current generic end ('>') is located after 292 * {@link TokenTypes#METHOD_REF method reference operator}. 293 * 294 * @param genericEnd {@link TokenTypes#GENERIC_END} 295 * @return true if '>' follows after method reference. 296 */ 297 private static boolean isAfterMethodReference(DetailAST genericEnd) { 298 return genericEnd.getParent().getParent().getType() == TokenTypes.METHOD_REF; 299 } 300 301 /** 302 * Checks the token for the start of Generics. 303 * 304 * @param ast the token to check 305 */ 306 private void processStart(DetailAST ast) { 307 final String line = getLine(ast.getLineNo() - 1); 308 final int before = ast.getColumnNo() - 1; 309 final int after = ast.getColumnNo() + 1; 310 311 // Need to handle two cases as in: 312 // 313 // public static <T> Callable<T> callable(Runnable task, T result) 314 // ^ ^ 315 // ws reqd ---+ +--- whitespace NOT required 316 // 317 if (before >= 0) { 318 // Detect if the first case 319 final DetailAST parent = ast.getParent(); 320 final DetailAST grandparent = parent.getParent(); 321 if (grandparent.getType() == TokenTypes.CTOR_DEF 322 || grandparent.getType() == TokenTypes.METHOD_DEF 323 || isGenericBeforeCtor(ast)) { 324 // Require whitespace 325 if (!Character.isWhitespace(line.charAt(before))) { 326 log(ast, MSG_WS_NOT_PRECEDED, OPEN_ANGLE_BRACKET); 327 } 328 } 329 // Whitespace not required 330 else if (Character.isWhitespace(line.charAt(before)) 331 && !containsWhitespaceBefore(before, line)) { 332 log(ast, MSG_WS_PRECEDED, OPEN_ANGLE_BRACKET); 333 } 334 } 335 336 if (after < line.length() 337 && Character.isWhitespace(line.charAt(after))) { 338 log(ast, MSG_WS_FOLLOWED, OPEN_ANGLE_BRACKET); 339 } 340 } 341 342 /** 343 * Returns whether the specified string contains only whitespace between 344 * specified indices. 345 * 346 * @param fromIndex the index to start the search from. Inclusive 347 * @param toIndex the index to finish the search. Exclusive 348 * @param line the line to check 349 * @return whether there are only whitespaces (or nothing) 350 */ 351 private static boolean containsWhitespaceBetween(int fromIndex, int toIndex, String line) { 352 boolean result = true; 353 for (int i = fromIndex; i < toIndex; i++) { 354 if (!Character.isWhitespace(line.charAt(i))) { 355 result = false; 356 break; 357 } 358 } 359 return result; 360 } 361 362 /** 363 * Returns whether the specified string contains only whitespace up to specified index. 364 * 365 * @param before the index to start the search from. Inclusive 366 * @param line the index to finish the search. Exclusive 367 * @return {@code true} if there are only whitespaces, 368 * false if there is nothing before or some other characters 369 */ 370 private static boolean containsWhitespaceBefore(int before, String line) { 371 return before != 0 && CommonUtil.hasWhitespaceBefore(before, line); 372 } 373 374 /** 375 * Checks whether given character is valid to be right after generic ends. 376 * 377 * @param charAfter character to check 378 * @return checks if given character is valid 379 */ 380 private static boolean isCharacterValidAfterGenericEnd(char charAfter) { 381 return charAfter == '(' || charAfter == ')' 382 || charAfter == ',' || charAfter == '[' 383 || charAfter == '.' || charAfter == ':' 384 || charAfter == ';' 385 || Character.isWhitespace(charAfter); 386 } 387 388}