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