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