1 //////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code for adherence to a set of rules. 3 // Copyright (C) 2001-2019 the original author or authors. 4 // 5 // This library is free software; you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 2.1 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library; if not, write to the Free Software 17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 //////////////////////////////////////////////////////////////////////////////// 19 20 package com.puppycrawl.tools.checkstyle.api; 21 22 import java.util.Collections; 23 import java.util.HashSet; 24 import java.util.Set; 25 import java.util.SortedSet; 26 import java.util.TreeSet; 27 28 import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 29 30 /** 31 * The base class for checks. 32 * 33 * @see <a href="{@docRoot}/../writingchecks.html" target="_top">Writing 34 * your own checks</a> 35 * @noinspection NoopMethodInAbstractClass 36 */ 37 public abstract class AbstractCheck extends AbstractViolationReporter { 38 39 /** 40 * The check context. 41 * @noinspection ThreadLocalNotStaticFinal 42 */ 43 private final ThreadLocal<FileContext> context = ThreadLocal.withInitial(FileContext::new); 44 45 /** The tokens the check is interested in. */ 46 private final Set<String> tokens = new HashSet<>(); 47 48 /** The tab width for column reporting. */ 49 private int tabWidth = CommonUtil.DEFAULT_TAB_WIDTH; 50 51 /** 52 * The class loader to load external classes. Not initialized as this must 53 * be set by my creator. 54 */ 55 private ClassLoader classLoader; 56 57 /** 58 * Returns the default token a check is interested in. Only used if the 59 * configuration for a check does not define the tokens. 60 * @return the default tokens 61 * @see TokenTypes 62 */ 63 public abstract int[] getDefaultTokens(); 64 65 /** 66 * The configurable token set. 67 * Used to protect Checks against malicious users who specify an 68 * unacceptable token set in the configuration file. 69 * The default implementation returns the check's default tokens. 70 * @return the token set this check is designed for. 71 * @see TokenTypes 72 */ 73 public abstract int[] getAcceptableTokens(); 74 75 /** 76 * The tokens that this check must be registered for. 77 * @return the token set this must be registered for. 78 * @see TokenTypes 79 */ 80 public abstract int[] getRequiredTokens(); 81 82 /** 83 * Whether comment nodes are required or not. 84 * @return false as a default value. 85 */ 86 public boolean isCommentNodesRequired() { 87 return false; 88 } 89 90 /** 91 * Adds a set of tokens the check is interested in. 92 * @param strRep the string representation of the tokens interested in 93 * @noinspection WeakerAccess 94 */ 95 public final void setTokens(String... strRep) { 96 Collections.addAll(tokens, strRep); 97 } 98 99 /** 100 * Returns the tokens registered for the check. 101 * @return the set of token names 102 */ 103 public final Set<String> getTokenNames() { 104 return Collections.unmodifiableSet(tokens); 105 } 106 107 /** 108 * Returns the sorted set of {@link LocalizedMessage}. 109 * @return the sorted set of {@link LocalizedMessage}. 110 */ 111 public SortedSet<LocalizedMessage> getMessages() { 112 return new TreeSet<>(context.get().messages); 113 } 114 115 /** 116 * Clears the sorted set of {@link LocalizedMessage} of the check. 117 */ 118 public final void clearMessages() { 119 context.get().messages.clear(); 120 } 121 122 /** 123 * Initialize the check. This is the time to verify that the check has 124 * everything required to perform it job. 125 */ 126 public void init() { 127 // No code by default, should be overridden only by demand at subclasses 128 } 129 130 /** 131 * Destroy the check. It is being retired from service. 132 */ 133 public void destroy() { 134 // No code by default, should be overridden only by demand at subclasses 135 } 136 137 /** 138 * Called before the starting to process a tree. Ideal place to initialize 139 * information that is to be collected whilst processing a tree. 140 * @param rootAST the root of the tree 141 */ 142 public void beginTree(DetailAST rootAST) { 143 // No code by default, should be overridden only by demand at subclasses 144 } 145 146 /** 147 * Called after finished processing a tree. Ideal place to report on 148 * information collected whilst processing a tree. 149 * @param rootAST the root of the tree 150 */ 151 public void finishTree(DetailAST rootAST) { 152 // No code by default, should be overridden only by demand at subclasses 153 } 154 155 /** 156 * Called to process a token. 157 * @param ast the token to process 158 */ 159 public void visitToken(DetailAST ast) { 160 // No code by default, should be overridden only by demand at subclasses 161 } 162 163 /** 164 * Called after all the child nodes have been process. 165 * @param ast the token leaving 166 */ 167 public void leaveToken(DetailAST ast) { 168 // No code by default, should be overridden only by demand at subclasses 169 } 170 171 /** 172 * Returns the lines associated with the tree. 173 * @return the file contents 174 */ 175 public final String[] getLines() { 176 return context.get().fileContents.getLines(); 177 } 178 179 /** 180 * Returns the line associated with the tree. 181 * @param index index of the line 182 * @return the line from the file contents 183 */ 184 public final String getLine(int index) { 185 return context.get().fileContents.getLine(index); 186 } 187 188 /** 189 * Set the file contents associated with the tree. 190 * @param contents the manager 191 */ 192 public final void setFileContents(FileContents contents) { 193 context.get().fileContents = contents; 194 } 195 196 /** 197 * Returns the file contents associated with the tree. 198 * @return the file contents 199 * @noinspection WeakerAccess 200 */ 201 public final FileContents getFileContents() { 202 return context.get().fileContents; 203 } 204 205 /** 206 * Set the class loader associated with the tree. 207 * @param classLoader the class loader 208 */ 209 public final void setClassLoader(ClassLoader classLoader) { 210 this.classLoader = classLoader; 211 } 212 213 /** 214 * Returns the class loader associated with the tree. 215 * @return the class loader 216 */ 217 public final ClassLoader getClassLoader() { 218 return classLoader; 219 } 220 221 /** 222 * Get tab width to report errors with. 223 * @return the tab width to report errors with 224 */ 225 protected final int getTabWidth() { 226 return tabWidth; 227 } 228 229 /** 230 * Set the tab width to report errors with. 231 * @param tabWidth an {@code int} value 232 */ 233 public final void setTabWidth(int tabWidth) { 234 this.tabWidth = tabWidth; 235 } 236 237 /** 238 * Helper method to log a LocalizedMessage. 239 * 240 * @param ast a node to get line id column numbers associated 241 * with the message 242 * @param key key to locale message format 243 * @param args arguments to format 244 */ 245 public final void log(DetailAST ast, String key, Object... args) { 246 // CommonUtil.lengthExpandedTabs returns column number considering tabulation 247 // characters, it takes line from the file by line number, ast column number and tab 248 // width as arguments. Returned value is 0-based, but user must see column number starting 249 // from 1, that is why result of the method CommonUtil.lengthExpandedTabs 250 // is increased by one. 251 252 final int col = 1 + CommonUtil.lengthExpandedTabs( 253 getLines()[ast.getLineNo() - 1], ast.getColumnNo(), tabWidth); 254 context.get().messages.add( 255 new LocalizedMessage( 256 ast.getLineNo(), 257 col, 258 ast.getColumnNo(), 259 ast.getType(), 260 getMessageBundle(), 261 key, 262 args, 263 getSeverityLevel(), 264 getId(), 265 getClass(), 266 getCustomMessages().get(key))); 267 } 268 269 @Override 270 public final void log(int line, String key, Object... args) { 271 context.get().messages.add( 272 new LocalizedMessage( 273 line, 274 getMessageBundle(), 275 key, 276 args, 277 getSeverityLevel(), 278 getId(), 279 getClass(), 280 getCustomMessages().get(key))); 281 } 282 283 @Override 284 public final void log(int lineNo, int colNo, String key, 285 Object... args) { 286 final int col = 1 + CommonUtil.lengthExpandedTabs( 287 getLines()[lineNo - 1], colNo, tabWidth); 288 context.get().messages.add( 289 new LocalizedMessage( 290 lineNo, 291 col, 292 getMessageBundle(), 293 key, 294 args, 295 getSeverityLevel(), 296 getId(), 297 getClass(), 298 getCustomMessages().get(key))); 299 } 300 301 /** 302 * The actual context holder. 303 */ 304 private static class FileContext { 305 306 /** The sorted set for collecting messages. */ 307 private final SortedSet<LocalizedMessage> messages = new TreeSet<>(); 308 309 /** The current file contents. */ 310 private FileContents fileContents; 311 312 } 313 314 }