001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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.api; 021 022import java.util.Collections; 023import java.util.HashSet; 024import java.util.Set; 025import java.util.SortedSet; 026import java.util.TreeSet; 027 028import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 029 030/** 031 * The base class for checks. 032 * 033 * @see <a href="{@docRoot}/../writingchecks.html" target="_top">Writing 034 * your own checks</a> 035 * @noinspection NoopMethodInAbstractClass 036 */ 037public abstract class AbstractCheck extends AbstractViolationReporter { 038 039 /** 040 * The check context. 041 * 042 * @noinspection ThreadLocalNotStaticFinal 043 */ 044 private final ThreadLocal<FileContext> context = ThreadLocal.withInitial(FileContext::new); 045 046 /** The tokens the check is interested in. */ 047 private final Set<String> tokens = new HashSet<>(); 048 049 /** The tab width for column reporting. */ 050 private int tabWidth = CommonUtil.DEFAULT_TAB_WIDTH; 051 052 /** 053 * Returns the default token a check is interested in. Only used if the 054 * configuration for a check does not define the tokens. 055 * 056 * @return the default tokens 057 * @see TokenTypes 058 */ 059 public abstract int[] getDefaultTokens(); 060 061 /** 062 * The configurable token set. 063 * Used to protect Checks against malicious users who specify an 064 * unacceptable token set in the configuration file. 065 * The default implementation returns the check's default tokens. 066 * 067 * @return the token set this check is designed for. 068 * @see TokenTypes 069 */ 070 public abstract int[] getAcceptableTokens(); 071 072 /** 073 * The tokens that this check must be registered for. 074 * 075 * @return the token set this must be registered for. 076 * @see TokenTypes 077 */ 078 public abstract int[] getRequiredTokens(); 079 080 /** 081 * Whether comment nodes are required or not. 082 * 083 * @return false as a default value. 084 */ 085 public boolean isCommentNodesRequired() { 086 return false; 087 } 088 089 /** 090 * Adds a set of tokens the check is interested in. 091 * 092 * @param strRep the string representation of the tokens interested in 093 * @noinspection WeakerAccess 094 */ 095 public final void setTokens(String... strRep) { 096 Collections.addAll(tokens, strRep); 097 } 098 099 /** 100 * Returns the tokens registered for the check. 101 * 102 * @return the set of token names 103 */ 104 public final Set<String> getTokenNames() { 105 return Collections.unmodifiableSet(tokens); 106 } 107 108 /** 109 * Returns the sorted set of {@link LocalizedMessage}. 110 * 111 * @return the sorted set of {@link LocalizedMessage}. 112 */ 113 public SortedSet<LocalizedMessage> getMessages() { 114 return new TreeSet<>(context.get().messages); 115 } 116 117 /** 118 * Clears the sorted set of {@link LocalizedMessage} of the check. 119 */ 120 public final void clearMessages() { 121 context.get().messages.clear(); 122 } 123 124 /** 125 * Initialize the check. This is the time to verify that the check has 126 * everything required to perform it job. 127 */ 128 public void init() { 129 // No code by default, should be overridden only by demand at subclasses 130 } 131 132 /** 133 * Destroy the check. It is being retired from service. 134 */ 135 public void destroy() { 136 context.remove(); 137 } 138 139 /** 140 * Called before the starting to process a tree. Ideal place to initialize 141 * information that is to be collected whilst processing a tree. 142 * 143 * @param rootAST the root of the tree 144 */ 145 public void beginTree(DetailAST rootAST) { 146 // No code by default, should be overridden only by demand at subclasses 147 } 148 149 /** 150 * Called after finished processing a tree. Ideal place to report on 151 * information collected whilst processing a tree. 152 * 153 * @param rootAST the root of the tree 154 */ 155 public void finishTree(DetailAST rootAST) { 156 // No code by default, should be overridden only by demand at subclasses 157 } 158 159 /** 160 * Called to process a token. 161 * 162 * @param ast the token to process 163 */ 164 public void visitToken(DetailAST ast) { 165 // No code by default, should be overridden only by demand at subclasses 166 } 167 168 /** 169 * Called after all the child nodes have been process. 170 * 171 * @param ast the token leaving 172 */ 173 public void leaveToken(DetailAST ast) { 174 // No code by default, should be overridden only by demand at subclasses 175 } 176 177 /** 178 * Set the file contents associated with the tree. 179 * 180 * @param contents the manager 181 */ 182 public final void setFileContents(FileContents contents) { 183 context.get().fileContents = contents; 184 } 185 186 /** 187 * Returns the file contents associated with the tree. 188 * 189 * @return the file contents 190 * @noinspection WeakerAccess 191 */ 192 public final FileContents getFileContents() { 193 return context.get().fileContents; 194 } 195 196 /** 197 * Get tab width to report audit events with. 198 * 199 * @return the tab width to audit events with 200 */ 201 protected final int getTabWidth() { 202 return tabWidth; 203 } 204 205 /** 206 * Set the tab width to report audit events with. 207 * 208 * @param tabWidth an {@code int} value 209 */ 210 public final void setTabWidth(int tabWidth) { 211 this.tabWidth = tabWidth; 212 } 213 214 @Override 215 public final void log(int line, String key, Object... args) { 216 context.get().messages.add( 217 new LocalizedMessage( 218 line, 219 getMessageBundle(), 220 key, 221 args, 222 getSeverityLevel(), 223 getId(), 224 getClass(), 225 getCustomMessages().get(key))); 226 } 227 228 @Override 229 public final void log(int lineNo, int colNo, String key, 230 Object... args) { 231 final int col = 1 + CommonUtil.lengthExpandedTabs( 232 getLines()[lineNo - 1], colNo, tabWidth); 233 context.get().messages.add( 234 new LocalizedMessage( 235 lineNo, 236 col, 237 getMessageBundle(), 238 key, 239 args, 240 getSeverityLevel(), 241 getId(), 242 getClass(), 243 getCustomMessages().get(key))); 244 } 245 246 /** 247 * Helper method to log a LocalizedMessage. 248 * 249 * @param ast a node to get line id column numbers associated 250 * with the message 251 * @param key key to locale message format 252 * @param args arguments to format 253 */ 254 public final void log(DetailAST ast, String key, Object... args) { 255 // CommonUtil.lengthExpandedTabs returns column number considering tabulation 256 // characters, it takes line from the file by line number, ast column number and tab 257 // width as arguments. Returned value is 0-based, but user must see column number starting 258 // from 1, that is why result of the method CommonUtil.lengthExpandedTabs 259 // is increased by one. 260 261 final int col = 1 + CommonUtil.lengthExpandedTabs( 262 getLines()[ast.getLineNo() - 1], ast.getColumnNo(), tabWidth); 263 context.get().messages.add( 264 new LocalizedMessage( 265 ast.getLineNo(), 266 col, 267 ast.getColumnNo(), 268 ast.getType(), 269 getMessageBundle(), 270 key, 271 args, 272 getSeverityLevel(), 273 getId(), 274 getClass(), 275 getCustomMessages().get(key))); 276 } 277 278 /** 279 * Returns the lines associated with the tree. 280 * 281 * @return the file contents 282 */ 283 public final String[] getLines() { 284 return context.get().fileContents.getLines(); 285 } 286 287 /** 288 * Returns the line associated with the tree. 289 * 290 * @param index index of the line 291 * @return the line from the file contents 292 */ 293 public final String getLine(int index) { 294 return context.get().fileContents.getLine(index); 295 } 296 297 /** 298 * The actual context holder. 299 */ 300 private static class FileContext { 301 302 /** The sorted set for collecting messages. */ 303 private final SortedSet<LocalizedMessage> messages = new TreeSet<>(); 304 305 /** The current file contents. */ 306 private FileContents fileContents; 307 308 } 309 310}