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