001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2014 Oliver Burn 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//////////////////////////////////////////////////////////////////////////////// 019package com.puppycrawl.tools.checkstyle.checks.whitespace; 020 021import com.puppycrawl.tools.checkstyle.api.Check; 022import com.puppycrawl.tools.checkstyle.api.DetailAST; 023import com.puppycrawl.tools.checkstyle.api.TokenTypes; 024import com.puppycrawl.tools.checkstyle.api.Utils; 025 026/** 027 * <p> 028 * Checks that the whitespace around the Generic tokens (angle brackets) 029 * "<" and ">" are correct to the <i>typical</i> convention. 030 * The convention is not configurable. 031 * </p> 032 * <br> 033 * <p> 034 * Left angle bracket ("<"): 035 * </p> 036 * <br> 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 * <br> 045 * <p> 046 * Right angle bracket (">"): 047 * </p> 048 * <br> 049 * <ul> 050 * <li> should not be preceded with whitespace in all cases.</li> 051 * <li> should be followed with whitespace in almost all cases, 052 * except diamond operators and when preceding method name.</li></ul> 053 * <br> 054 * <p> 055 * Examples with correct spacing: 056 * </p> 057 * <br> 058 * <pre> 059 * public void <K, V extends Number> boolean foo(K, V) {} // Generic methods definitions 060 * class name<T1, T2, ..., Tn> {} // Generic type definition 061 * OrderedPair<String, Box<Integer>> p; // Generic type reference 062 * boolean same = Util.<Integer, String>compare(p1, p2); // Generic preceded method name 063 * Pair<Integer, String> p1 = new Pair<>(1, "apple");// Diamond operator 064 * </pre> 065 * @author Oliver Burn 066 */ 067public class GenericWhitespaceCheck extends Check 068{ 069 /** Used to count the depth of a Generic expression. */ 070 private int depth; 071 072 @Override 073 public int[] getDefaultTokens() 074 { 075 return new int[] {TokenTypes.GENERIC_START, TokenTypes.GENERIC_END}; 076 } 077 078 @Override 079 public void beginTree(DetailAST rootAST) 080 { 081 // Reset for each tree, just incase there are errors in preceeding 082 // trees. 083 depth = 0; 084 } 085 086 @Override 087 public void visitToken(DetailAST ast) 088 { 089 if (ast.getType() == TokenTypes.GENERIC_START) { 090 processStart(ast); 091 depth++; 092 } 093 else if (ast.getType() == TokenTypes.GENERIC_END) { 094 processEnd(ast); 095 depth--; 096 } 097 } 098 099 /** 100 * Checks the token for the end of Generics. 101 * @param ast the token to check 102 */ 103 private void processEnd(DetailAST ast) 104 { 105 final String line = getLine(ast.getLineNo() - 1); 106 final int before = ast.getColumnNo() - 1; 107 final int after = ast.getColumnNo() + 1; 108 109 if ((0 <= before) && Character.isWhitespace(line.charAt(before)) 110 && !Utils.whitespaceBefore(before, line)) 111 { 112 log(ast.getLineNo(), before, "ws.preceded", ">"); 113 } 114 115 if (after < line.length()) { 116 117 // Check if the last Generic, in which case must be a whitespace 118 // or a '(),[.'. 119 if (1 == depth) { 120 final char charAfter = line.charAt(after); 121 122 // Need to handle a number of cases. First is: 123 // Collections.<Object>emptySet(); 124 // ^ 125 // +--- whitespace not allowed 126 if ((ast.getParent().getType() == TokenTypes.TYPE_ARGUMENTS) 127 && (ast.getParent().getParent().getType() 128 == TokenTypes.DOT) 129 && (ast.getParent().getParent().getParent().getType() 130 == TokenTypes.METHOD_CALL)) 131 { 132 if (Character.isWhitespace(charAfter)) { 133 log(ast.getLineNo(), after, "ws.followed", ">"); 134 } 135 } 136 else if (!Character.isWhitespace(charAfter) 137 && ('(' != charAfter) && (')' != charAfter) 138 && (',' != charAfter) && ('[' != charAfter) 139 && ('.' != charAfter) && (':' != charAfter)) 140 { 141 log(ast.getLineNo(), after, "ws.illegalFollow", ">"); 142 } 143 } 144 else { 145 // In a nested Generic type, so can only be a '>' or ',' or '&' 146 147 // In case of several extends definitions: 148 // 149 // class IntEnumValueType<E extends Enum<E> & IntEnum> 150 // ^ 151 // should be whitespace if followed by & -+ 152 // 153 final int indexOfAmp = line.indexOf('&', after); 154 if ((indexOfAmp != -1) 155 && whitespaceBetween(after, indexOfAmp, line)) 156 { 157 if (indexOfAmp - after == 0) { 158 log(ast.getLineNo(), after, "ws.notPreceded", "&"); 159 } 160 else if (indexOfAmp - after != 1) { 161 log(ast.getLineNo(), after, "ws.followed", ">"); 162 } 163 } 164 else if (line.charAt(after) == ' ') { 165 log(ast.getLineNo(), after, "ws.followed", ">"); 166 } 167 } 168 } 169 } 170 171 /** 172 * Checks the token for the start of Generics. 173 * @param ast the token to check 174 */ 175 private void processStart(DetailAST ast) 176 { 177 final String line = getLine(ast.getLineNo() - 1); 178 final int before = ast.getColumnNo() - 1; 179 final int after = ast.getColumnNo() + 1; 180 181 // Need to handle two cases as in: 182 // 183 // public static <T> Callable<T> callable(Runnable task, T result) 184 // ^ ^ 185 // ws reqd ---+ +--- whitespace NOT required 186 // 187 if (0 <= before) { 188 // Detect if the first case 189 final DetailAST parent = ast.getParent(); 190 final DetailAST grandparent = parent.getParent(); 191 if ((TokenTypes.TYPE_PARAMETERS == parent.getType()) 192 && ((TokenTypes.CTOR_DEF == grandparent.getType()) 193 || (TokenTypes.METHOD_DEF == grandparent.getType()))) 194 { 195 // Require whitespace 196 if (!Character.isWhitespace(line.charAt(before))) { 197 log(ast.getLineNo(), before, "ws.notPreceded", "<"); 198 } 199 } 200 // Whitespace not required 201 else if (Character.isWhitespace(line.charAt(before)) 202 && !Utils.whitespaceBefore(before, line)) 203 { 204 log(ast.getLineNo(), before, "ws.preceded", "<"); 205 } 206 } 207 208 if ((after < line.length()) 209 && Character.isWhitespace(line.charAt(after))) 210 { 211 log(ast.getLineNo(), after, "ws.followed", "<"); 212 } 213 } 214 215 /** 216 * Returns whether the specified string contains only whitespace between 217 * specified indices. 218 * 219 * @param fromIndex the index to start the search from. Inclusive 220 * @param toIndex the index to finish the search. Exclusive 221 * @param line the line to check 222 * @return whether there are only whitespaces (or nothing) 223 */ 224 private static boolean whitespaceBetween( 225 int fromIndex, int toIndex, String line) 226 { 227 for (int i = fromIndex; i < toIndex; i++) { 228 if (!Character.isWhitespace(line.charAt(i))) { 229 return false; 230 } 231 } 232 return true; 233 } 234}