1 //////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code for adherence to a set of rules. 3 // Copyright (C) 2001-2020 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 * 42 * @noinspection ThreadLocalNotStaticFinal 43 */ 44 private final ThreadLocal<FileContext> context = ThreadLocal.withInitial(FileContext::new); 45 46 /** The tokens the check is interested in. */ 47 private final Set<String> tokens = new HashSet<>(); 48 49 /** The tab width for column reporting. */ 50 private int tabWidth = CommonUtil.DEFAULT_TAB_WIDTH; 51 52 /** 53 * Returns the default token a check is interested in. Only used if the 54 * configuration for a check does not define the tokens. 55 * 56 * @return the default tokens 57 * @see TokenTypes 58 */ 59 public abstract int[] getDefaultTokens(); 60 61 /** 62 * The configurable token set. 63 * Used to protect Checks against malicious users who specify an 64 * unacceptable token set in the configuration file. 65 * The default implementation returns the check's default tokens. 66 * 67 * @return the token set this check is designed for. 68 * @see TokenTypes 69 */ 70 public abstract int[] getAcceptableTokens(); 71 72 /** 73 * The tokens that this check must be registered for. 74 * 75 * @return the token set this must be registered for. 76 * @see TokenTypes 77 */ 78 public abstract int[] getRequiredTokens(); 79 80 /** 81 * Whether comment nodes are required or not. 82 * 83 * @return false as a default value. 84 */ 85 public boolean isCommentNodesRequired() { 86 return false; 87 } 88 89 /** 90 * Adds a set of tokens the check is interested in. 91 * 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 * 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 }