001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2017 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 /** Default tab width for column reporting. */ 040 private static final int DEFAULT_TAB_WIDTH = 8; 041 042 /** 043 * The check context. 044 * @noinspection ThreadLocalNotStaticFinal 045 */ 046 private final ThreadLocal<FileContext> context = ThreadLocal.withInitial(FileContext::new); 047 048 /** The tokens the check is interested in. */ 049 private final Set<String> tokens = new HashSet<>(); 050 051 /** The tab width for column reporting. */ 052 private int tabWidth = DEFAULT_TAB_WIDTH; 053 054 /** 055 * The class loader to load external classes. Not initialized as this must 056 * be set by my creator. 057 */ 058 private ClassLoader classLoader; 059 060 /** 061 * Returns the default token a check is interested in. Only used if the 062 * configuration for a check does not define the tokens. 063 * @return the default tokens 064 * @see TokenTypes 065 */ 066 public abstract int[] getDefaultTokens(); 067 068 /** 069 * The configurable token set. 070 * Used to protect Checks against malicious users who specify an 071 * unacceptable token set in the configuration file. 072 * The default implementation returns the check's default tokens. 073 * @return the token set this check is designed for. 074 * @see TokenTypes 075 */ 076 public abstract int[] getAcceptableTokens(); 077 078 /** 079 * The tokens that this check must be registered for. 080 * @return the token set this must be registered for. 081 * @see TokenTypes 082 */ 083 public abstract int[] getRequiredTokens(); 084 085 /** 086 * Whether comment nodes are required or not. 087 * @return false as a default value. 088 */ 089 public boolean isCommentNodesRequired() { 090 return false; 091 } 092 093 /** 094 * Adds a set of tokens the check is interested in. 095 * @param strRep the string representation of the tokens interested in 096 * @noinspection WeakerAccess 097 */ 098 public final void setTokens(String... strRep) { 099 Collections.addAll(tokens, strRep); 100 } 101 102 /** 103 * Returns the tokens registered for the check. 104 * @return the set of token names 105 */ 106 public final Set<String> getTokenNames() { 107 return Collections.unmodifiableSet(tokens); 108 } 109 110 /** 111 * Returns the sorted set of {@link LocalizedMessage}. 112 * @return the sorted set of {@link LocalizedMessage}. 113 */ 114 public SortedSet<LocalizedMessage> getMessages() { 115 return new TreeSet<>(context.get().messages); 116 } 117 118 /** 119 * Clears the sorted set of {@link LocalizedMessage} of the check. 120 */ 121 public final void clearMessages() { 122 context.get().messages.clear(); 123 } 124 125 /** 126 * Initialize the check. This is the time to verify that the check has 127 * everything required to perform it job. 128 */ 129 public void init() { 130 // No code by default, should be overridden only by demand at subclasses 131 } 132 133 /** 134 * Destroy the check. It is being retired from service. 135 */ 136 public void destroy() { 137 // No code by default, should be overridden only by demand at subclasses 138 } 139 140 /** 141 * Called before the starting to process a tree. Ideal place to initialize 142 * information that is to be collected whilst processing a tree. 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 * @param rootAST the root of the tree 153 */ 154 public void finishTree(DetailAST rootAST) { 155 // No code by default, should be overridden only by demand at subclasses 156 } 157 158 /** 159 * Called to process a token. 160 * @param ast the token to process 161 */ 162 public void visitToken(DetailAST ast) { 163 // No code by default, should be overridden only by demand at subclasses 164 } 165 166 /** 167 * Called after all the child nodes have been process. 168 * @param ast the token leaving 169 */ 170 public void leaveToken(DetailAST ast) { 171 // No code by default, should be overridden only by demand at subclasses 172 } 173 174 /** 175 * Returns the lines associated with the tree. 176 * @return the file contents 177 */ 178 public final String[] getLines() { 179 return context.get().fileContents.getLines(); 180 } 181 182 /** 183 * Returns the line associated with the tree. 184 * @param index index of the line 185 * @return the line from the file contents 186 */ 187 public final String getLine(int index) { 188 return context.get().fileContents.getLine(index); 189 } 190 191 /** 192 * Set the file contents associated with the tree. 193 * @param contents the manager 194 */ 195 public final void setFileContents(FileContents contents) { 196 context.get().fileContents = contents; 197 } 198 199 /** 200 * Returns the file contents associated with the tree. 201 * @return the file contents 202 * @noinspection WeakerAccess 203 */ 204 public final FileContents getFileContents() { 205 return context.get().fileContents; 206 } 207 208 /** 209 * Set the class loader associated with the tree. 210 * @param classLoader the class loader 211 */ 212 public final void setClassLoader(ClassLoader classLoader) { 213 this.classLoader = classLoader; 214 } 215 216 /** 217 * Returns the class loader associated with the tree. 218 * @return the class loader 219 */ 220 public final ClassLoader getClassLoader() { 221 return classLoader; 222 } 223 224 /** 225 * Get tab width to report errors with. 226 * @return the tab width to report errors with 227 */ 228 protected final int getTabWidth() { 229 return tabWidth; 230 } 231 232 /** 233 * Set the tab width to report errors with. 234 * @param tabWidth an {@code int} value 235 */ 236 public final void setTabWidth(int tabWidth) { 237 this.tabWidth = tabWidth; 238 } 239 240 /** 241 * Helper method to log a LocalizedMessage. 242 * 243 * @param ast a node to get line id column numbers associated 244 * with the message 245 * @param key key to locale message format 246 * @param args arguments to format 247 */ 248 public final void log(DetailAST ast, String key, Object... args) { 249 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 /** The sorted set for collecting messages. */ 310 private final SortedSet<LocalizedMessage> messages = new TreeSet<>(); 311 312 /** The current file contents. */ 313 private FileContents fileContents; 314 } 315}