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