001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2017 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.api.AbstractCheck; 023import com.puppycrawl.tools.checkstyle.api.DetailAST; 024import com.puppycrawl.tools.checkstyle.api.TokenTypes; 025import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 026 027/** 028 * <p> 029 * Checks that the whitespace around the Generic tokens (angle brackets) 030 * "<" and ">" are correct to the <i>typical</i> convention. 031 * The convention is not configurable. 032 * </p> 033 * <br> 034 * <p> 035 * Left angle bracket ("<"): 036 * </p> 037 * <br> 038 * <ul> 039 * <li> should be preceded with whitespace only 040 * in generic methods definitions.</li> 041 * <li> should not be preceded with whitespace 042 * when it is precede method name or following type name.</li> 043 * <li> should not be followed with whitespace in all cases.</li> 044 * </ul> 045 * <br> 046 * <p> 047 * Right angle bracket (">"): 048 * </p> 049 * <br> 050 * <ul> 051 * <li> should not be preceded with whitespace in all cases.</li> 052 * <li> should be followed with whitespace in almost all cases, 053 * except diamond operators and when preceding method name.</li></ul> 054 * <br> 055 * <p> 056 * Examples with correct spacing: 057 * </p> 058 * <br> 059 * <pre> 060 * public void <K, V extends Number> boolean foo(K, V) {} // Generic methods definitions 061 * class name<T1, T2, ..., Tn> {} // Generic type definition 062 * OrderedPair<String, Box<Integer>> p; // Generic type reference 063 * boolean same = Util.<Integer, String>compare(p1, p2); // Generic preceded method name 064 * Pair<Integer, String> p1 = new Pair<>(1, "apple");// Diamond operator 065 * List<T> list = ImmutableList.Builder<T>::new; // Method reference 066 * sort(list, Comparable::<String>compareTo); // Method reference 067 * </pre> 068 * @author Oliver Burn 069 */ 070public class GenericWhitespaceCheck extends AbstractCheck { 071 072 /** 073 * A key is pointing to the warning message text in "messages.properties" 074 * file. 075 */ 076 public static final String MSG_WS_PRECEDED = "ws.preceded"; 077 078 /** 079 * A key is pointing to the warning message text in "messages.properties" 080 * file. 081 */ 082 public static final String MSG_WS_FOLLOWED = "ws.followed"; 083 084 /** 085 * A key is pointing to the warning message text in "messages.properties" 086 * file. 087 */ 088 public static final String MSG_WS_NOT_PRECEDED = "ws.notPreceded"; 089 090 /** 091 * A key is pointing to the warning message text in "messages.properties" 092 * file. 093 */ 094 public static final String MSG_WS_ILLEGAL_FOLLOW = "ws.illegalFollow"; 095 096 /** Open angle bracket literal. */ 097 private static final String OPEN_ANGLE_BRACKET = "<"; 098 099 /** Close angle bracket literal. */ 100 private static final String CLOSE_ANGLE_BRACKET = ">"; 101 102 /** Used to count the depth of a Generic expression. */ 103 private int depth; 104 105 @Override 106 public int[] getDefaultTokens() { 107 return getAcceptableTokens(); 108 } 109 110 @Override 111 public int[] getAcceptableTokens() { 112 return new int[] {TokenTypes.GENERIC_START, TokenTypes.GENERIC_END}; 113 } 114 115 @Override 116 public int[] getRequiredTokens() { 117 return getAcceptableTokens(); 118 } 119 120 @Override 121 public void beginTree(DetailAST rootAST) { 122 // Reset for each tree, just increase there are errors in preceding 123 // trees. 124 depth = 0; 125 } 126 127 @Override 128 public void visitToken(DetailAST ast) { 129 switch (ast.getType()) { 130 case TokenTypes.GENERIC_START: 131 processStart(ast); 132 depth++; 133 break; 134 case TokenTypes.GENERIC_END: 135 processEnd(ast); 136 depth--; 137 break; 138 default: 139 throw new IllegalArgumentException("Unknown type " + ast); 140 } 141 } 142 143 /** 144 * Checks the token for the end of Generics. 145 * @param ast the token to check 146 */ 147 private void processEnd(DetailAST ast) { 148 final String line = getLine(ast.getLineNo() - 1); 149 final int before = ast.getColumnNo() - 1; 150 final int after = ast.getColumnNo() + 1; 151 152 if (before >= 0 && Character.isWhitespace(line.charAt(before)) 153 && !CommonUtils.hasWhitespaceBefore(before, line)) { 154 log(ast.getLineNo(), before, MSG_WS_PRECEDED, CLOSE_ANGLE_BRACKET); 155 } 156 157 if (after < line.length()) { 158 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 >= 0 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 && !CommonUtils.hasWhitespaceBefore(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 * Checks whether given character is valid to be right after generic ends. 307 * @param charAfter character to check 308 * @return checks if given character is valid 309 */ 310 private static boolean isCharacterValidAfterGenericEnd(char charAfter) { 311 return charAfter == '(' || charAfter == ')' 312 || charAfter == ',' || charAfter == '[' 313 || charAfter == '.' || charAfter == ':' 314 || charAfter == ';' 315 || Character.isWhitespace(charAfter); 316 } 317}