001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2024 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.io.File; 023import java.util.Arrays; 024import java.util.SortedSet; 025import java.util.TreeSet; 026 027import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 028 029/** 030 * Provides common functionality for many FileSetChecks. 031 * 032 * @noinspection NoopMethodInAbstractClass 033 * @noinspectionreason NoopMethodInAbstractClass - we allow each 034 * check to define these methods, as needed. They 035 * should be overridden only by demand in subclasses 036 */ 037public abstract class AbstractFileSetCheck 038 extends AbstractViolationReporter 039 implements FileSetCheck { 040 041 /** The extension separator. */ 042 private static final String EXTENSION_SEPARATOR = "."; 043 044 /** 045 * The check context. 046 * 047 * @noinspection ThreadLocalNotStaticFinal 048 * @noinspectionreason ThreadLocalNotStaticFinal - static context is 049 * problematic for multithreading 050 */ 051 private final ThreadLocal<FileContext> context = ThreadLocal.withInitial(FileContext::new); 052 053 /** The dispatcher errors are fired to. */ 054 private MessageDispatcher messageDispatcher; 055 056 /** 057 * Specify the file extensions of the files to process. 058 * Default is uninitialized as the value is inherited from the parent module. 059 */ 060 private String[] fileExtensions; 061 062 /** 063 * The tab width for column reporting. 064 * Default is uninitialized as the value is inherited from the parent module. 065 */ 066 private int tabWidth; 067 068 /** 069 * Called to process a file that matches the specified file extensions. 070 * 071 * @param file the file to be processed 072 * @param fileText the contents of the file. 073 * @throws CheckstyleException if error condition within Checkstyle occurs. 074 */ 075 protected abstract void processFiltered(File file, FileText fileText) 076 throws CheckstyleException; 077 078 @Override 079 public void init() { 080 // No code by default, should be overridden only by demand at subclasses 081 } 082 083 @Override 084 public void destroy() { 085 context.remove(); 086 } 087 088 @Override 089 public void beginProcessing(String charset) { 090 // No code by default, should be overridden only by demand at subclasses 091 } 092 093 @Override 094 public final SortedSet<Violation> process(File file, FileText fileText) 095 throws CheckstyleException { 096 final FileContext fileContext = context.get(); 097 fileContext.fileContents = new FileContents(fileText); 098 fileContext.violations.clear(); 099 // Process only what interested in 100 if (CommonUtil.matchesFileExtension(file, fileExtensions)) { 101 processFiltered(file, fileText); 102 } 103 final SortedSet<Violation> result = new TreeSet<>(fileContext.violations); 104 fileContext.violations.clear(); 105 return result; 106 } 107 108 @Override 109 public void finishProcessing() { 110 // No code by default, should be overridden only by demand at subclasses 111 } 112 113 @Override 114 public final void setMessageDispatcher(MessageDispatcher messageDispatcher) { 115 this.messageDispatcher = messageDispatcher; 116 } 117 118 /** 119 * A message dispatcher is used to fire violations to 120 * interested audit listeners. 121 * 122 * @return the current MessageDispatcher. 123 */ 124 protected final MessageDispatcher getMessageDispatcher() { 125 return messageDispatcher; 126 } 127 128 /** 129 * Returns the sorted set of {@link Violation}. 130 * 131 * @return the sorted set of {@link Violation}. 132 */ 133 public SortedSet<Violation> getViolations() { 134 return new TreeSet<>(context.get().violations); 135 } 136 137 /** 138 * Set the file contents associated with the tree. 139 * 140 * @param contents the manager 141 */ 142 public final void setFileContents(FileContents contents) { 143 context.get().fileContents = contents; 144 } 145 146 /** 147 * Returns the file contents associated with the file. 148 * 149 * @return the file contents 150 */ 151 protected final FileContents getFileContents() { 152 return context.get().fileContents; 153 } 154 155 /** 156 * Makes copy of file extensions and returns them. 157 * 158 * @return file extensions that identify the files that pass the 159 * filter of this FileSetCheck. 160 */ 161 public String[] getFileExtensions() { 162 return Arrays.copyOf(fileExtensions, fileExtensions.length); 163 } 164 165 /** 166 * Setter to specify the file extensions of the files to process. 167 * 168 * @param extensions the set of file extensions. A missing 169 * initial '.' character of an extension is automatically added. 170 * @throws IllegalArgumentException is argument is null 171 */ 172 public final void setFileExtensions(String... extensions) { 173 if (extensions == null) { 174 throw new IllegalArgumentException("Extensions array can not be null"); 175 } 176 177 fileExtensions = new String[extensions.length]; 178 for (int i = 0; i < extensions.length; i++) { 179 final String extension = extensions[i]; 180 if (extension.startsWith(EXTENSION_SEPARATOR)) { 181 fileExtensions[i] = extension; 182 } 183 else { 184 fileExtensions[i] = EXTENSION_SEPARATOR + extension; 185 } 186 } 187 } 188 189 /** 190 * Get tab width to report audit events with. 191 * 192 * @return the tab width to report audit events with 193 */ 194 protected final int getTabWidth() { 195 return tabWidth; 196 } 197 198 /** 199 * Set the tab width to report audit events with. 200 * 201 * @param tabWidth an {@code int} value 202 */ 203 public final void setTabWidth(int tabWidth) { 204 this.tabWidth = tabWidth; 205 } 206 207 /** 208 * Adds the sorted set of {@link Violation} to the message collector. 209 * 210 * @param violations the sorted set of {@link Violation}. 211 */ 212 protected void addViolations(SortedSet<Violation> violations) { 213 context.get().violations.addAll(violations); 214 } 215 216 @Override 217 public final void log(int line, String key, Object... args) { 218 context.get().violations.add( 219 new Violation(line, 220 getMessageBundle(), 221 key, 222 args, 223 getSeverityLevel(), 224 getId(), 225 getClass(), 226 getCustomMessages().get(key))); 227 } 228 229 @Override 230 public final void log(int lineNo, int colNo, String key, 231 Object... args) { 232 final FileContext fileContext = context.get(); 233 final int col = 1 + CommonUtil.lengthExpandedTabs( 234 fileContext.fileContents.getLine(lineNo - 1), colNo, tabWidth); 235 fileContext.violations.add( 236 new Violation(lineNo, 237 col, 238 getMessageBundle(), 239 key, 240 args, 241 getSeverityLevel(), 242 getId(), 243 getClass(), 244 getCustomMessages().get(key))); 245 } 246 247 /** 248 * Notify all listeners about the errors in a file. 249 * Calls {@code MessageDispatcher.fireErrors()} with 250 * all logged errors and then clears errors' list. 251 * 252 * @param fileName the audited file 253 */ 254 protected final void fireErrors(String fileName) { 255 final FileContext fileContext = context.get(); 256 final SortedSet<Violation> errors = new TreeSet<>(fileContext.violations); 257 fileContext.violations.clear(); 258 messageDispatcher.fireErrors(fileName, errors); 259 } 260 261 /** 262 * The actual context holder. 263 */ 264 private static final class FileContext { 265 266 /** The sorted set for collecting violations. */ 267 private final SortedSet<Violation> violations = new TreeSet<>(); 268 269 /** The current file contents. */ 270 private FileContents fileContents; 271 272 } 273 274}