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 mDepth; 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 aRootAST) 080 { 081 // Reset for each tree, just incase there are errors in preceeding 082 // trees. 083 mDepth = 0; 084 } 085 086 @Override 087 public void visitToken(DetailAST aAST) 088 { 089 if (aAST.getType() == TokenTypes.GENERIC_START) { 090 processStart(aAST); 091 mDepth++; 092 } 093 else if (aAST.getType() == TokenTypes.GENERIC_END) { 094 processEnd(aAST); 095 mDepth--; 096 } 097 } 098 099 /** 100 * Checks the token for the end of Generics. 101 * @param aAST the token to check 102 */ 103 private void processEnd(DetailAST aAST) 104 { 105 final String line = getLines()[aAST.getLineNo() - 1]; 106 final int before = aAST.getColumnNo() - 1; 107 final int after = aAST.getColumnNo() + 1; 108 109 if ((0 <= before) && Character.isWhitespace(line.charAt(before)) 110 && !Utils.whitespaceBefore(before, line)) 111 { 112 log(aAST.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 == mDepth) { 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 ((aAST.getParent().getType() == TokenTypes.TYPE_ARGUMENTS) 127 && (aAST.getParent().getParent().getType() 128 == TokenTypes.DOT) 129 && (aAST.getParent().getParent().getParent().getType() 130 == TokenTypes.METHOD_CALL)) 131 { 132 if (Character.isWhitespace(charAfter)) { 133 log(aAST.getLineNo(), after, "ws.followed", ">"); 134 } 135 } 136 else if (!Character.isWhitespace(charAfter) 137 && ('(' != charAfter) && (')' != charAfter) 138 && (',' != charAfter) && ('[' != charAfter) 139 && ('.' != charAfter)) 140 { 141 log(aAST.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(aAST.getLineNo(), after, "ws.notPreceded", "&"); 159 } 160 else if (indexOfAmp - after != 1) { 161 log(aAST.getLineNo(), after, "ws.followed", ">"); 162 } 163 } 164 else if ((line.charAt(after) != '>') 165 && (line.charAt(after) != ',') 166 && (line.charAt(after) != '[')) 167 { 168 log(aAST.getLineNo(), after, "ws.followed", ">"); 169 } 170 } 171 } 172 } 173 174 /** 175 * Checks the token for the start of Generics. 176 * @param aAST the token to check 177 */ 178 private void processStart(DetailAST aAST) 179 { 180 final String line = getLines()[aAST.getLineNo() - 1]; 181 final int before = aAST.getColumnNo() - 1; 182 final int after = aAST.getColumnNo() + 1; 183 184 // Need to handle two cases as in: 185 // 186 // public static <T> Callable<T> callable(Runnable task, T result) 187 // ^ ^ 188 // ws reqd ---+ +--- whitespace NOT required 189 // 190 if (0 <= before) { 191 // Detect if the first case 192 final DetailAST parent = aAST.getParent(); 193 final DetailAST grandparent = parent.getParent(); 194 if ((TokenTypes.TYPE_PARAMETERS == parent.getType()) 195 && ((TokenTypes.CTOR_DEF == grandparent.getType()) 196 || (TokenTypes.METHOD_DEF == grandparent.getType()))) 197 { 198 // Require whitespace 199 if (!Character.isWhitespace(line.charAt(before))) { 200 log(aAST.getLineNo(), before, "ws.notPreceded", "<"); 201 } 202 } 203 // Whitespace not required 204 else if (Character.isWhitespace(line.charAt(before)) 205 && !Utils.whitespaceBefore(before, line)) 206 { 207 log(aAST.getLineNo(), before, "ws.preceded", "<"); 208 } 209 } 210 211 if ((after < line.length()) 212 && Character.isWhitespace(line.charAt(after))) 213 { 214 log(aAST.getLineNo(), after, "ws.followed", "<"); 215 } 216 } 217 218 /** 219 * Returns whether the specified string contains only whitespace between 220 * specified indices. 221 * 222 * @param aFromIndex the index to start the search from. Inclusive 223 * @param aToIndex the index to finish the search. Exclusive 224 * @param aLine the line to check 225 * @return whether there are only whitespaces (or nothing) 226 */ 227 private static boolean whitespaceBetween( 228 int aFromIndex, int aToIndex, String aLine) 229 { 230 for (int i = aFromIndex; i < aToIndex; i++) { 231 if (!Character.isWhitespace(aLine.charAt(i))) { 232 return false; 233 } 234 } 235 return true; 236 } 237}