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