001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2025 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.text.MessageFormat;
023import java.util.Arrays;
024import java.util.Locale;
025import java.util.Objects;
026
027import javax.annotation.Nullable;
028
029import com.puppycrawl.tools.checkstyle.LocalizedMessage;
030import com.puppycrawl.tools.checkstyle.utils.UnmodifiableCollectionUtil;
031
032/**
033 * Represents a violation that can be localised. The translations come from
034 * message.properties files. The underlying implementation uses
035 * java.text.MessageFormat.
036 *
037 * @noinspection ClassWithTooManyConstructors
038 * @noinspectionreason ClassWithTooManyConstructors - immutable nature of class requires a
039 * bunch of constructors
040 */
041public final class Violation
042 implements Comparable<Violation> {
043
044 /** The default severity level if one is not specified. */
045 private static final SeverityLevel DEFAULT_SEVERITY = SeverityLevel.ERROR;
046
047 /** The line number. **/
048 private final int lineNo;
049 /** The column number. **/
050 private final int columnNo;
051 /** The column char index. **/
052 private final int columnCharIndex;
053 /** The token type constant. See {@link TokenTypes}. **/
054 private final int tokenType;
055
056 /** The severity level. **/
057 private final SeverityLevel severityLevel;
058
059 /** The id of the module generating the violation. */
060 private final String moduleId;
061
062 /** Key for the violation format. **/
063 private final String key;
064
065 /** Arguments for MessageFormat. */
066 private final Object[] args;
067
068 /** Name of the resource bundle to get violations from. **/
069 private final String bundle;
070
071 /** Class of the source for this Violation. */
072 private final Class<?> sourceClass;
073
074 /** A custom violation overriding the default violation from the bundle. */
075 @Nullable
076 private final String customMessage;
077
078 /**
079 * Creates a new {@code Violation} instance.
080 *
081 * @param lineNo line number associated with the violation
082 * @param columnNo column number associated with the violation
083 * @param columnCharIndex column char index associated with the violation
084 * @param tokenType token type of the event associated with violation. See {@link TokenTypes}
085 * @param bundle resource bundle name
086 * @param key the key to locate the translation
087 * @param args arguments for the translation
088 * @param severityLevel severity level for the violation
089 * @param moduleId the id of the module the violation is associated with
090 * @param sourceClass the Class that is the source of the violation
091 * @param customMessage optional custom violation overriding the default
092 * @noinspection ConstructorWithTooManyParameters
093 * @noinspectionreason ConstructorWithTooManyParameters - immutable class requires a large
094 * number of arguments
095 */
096 // -@cs[ParameterNumber] Class is immutable, we need that amount of arguments.
097 public Violation(int lineNo,
098 int columnNo,
099 int columnCharIndex,
100 int tokenType,
101 String bundle,
102 String key,
103 Object[] args,
104 SeverityLevel severityLevel,
105 String moduleId,
106 Class<?> sourceClass,
107 @Nullable String customMessage) {
108 this.lineNo = lineNo;
109 this.columnNo = columnNo;
110 this.columnCharIndex = columnCharIndex;
111 this.tokenType = tokenType;
112 this.key = key;
113
114 if (args == null) {
115 this.args = null;
116 }
117 else {
118 this.args = UnmodifiableCollectionUtil.copyOfArray(args, args.length);
119 }
120 this.bundle = bundle;
121 this.severityLevel = severityLevel;
122 this.moduleId = moduleId;
123 this.sourceClass = sourceClass;
124 this.customMessage = customMessage;
125 }
126
127 /**
128 * Creates a new {@code Violation} instance.
129 *
130 * @param lineNo line number associated with the violation
131 * @param columnNo column number associated with the violation
132 * @param tokenType token type of the event associated with violation. See {@link TokenTypes}
133 * @param bundle resource bundle name
134 * @param key the key to locate the translation
135 * @param args arguments for the translation
136 * @param severityLevel severity level for the violation
137 * @param moduleId the id of the module the violation is associated with
138 * @param sourceClass the Class that is the source of the violation
139 * @param customMessage optional custom violation overriding the default
140 * @noinspection ConstructorWithTooManyParameters
141 * @noinspectionreason ConstructorWithTooManyParameters - immutable class requires a large
142 * number of arguments
143 */
144 // -@cs[ParameterNumber] Class is immutable, we need that amount of arguments.
145 public Violation(int lineNo,
146 int columnNo,
147 int tokenType,
148 String bundle,
149 String key,
150 Object[] args,
151 SeverityLevel severityLevel,
152 String moduleId,
153 Class<?> sourceClass,
154 @Nullable String customMessage) {
155 this(lineNo, columnNo, columnNo, tokenType, bundle, key, args, severityLevel, moduleId,
156 sourceClass, customMessage);
157 }
158
159 /**
160 * Creates a new {@code Violation} instance.
161 *
162 * @param lineNo line number associated with the violation
163 * @param columnNo column number associated with the violation
164 * @param bundle resource bundle name
165 * @param key the key to locate the translation
166 * @param args arguments for the translation
167 * @param severityLevel severity level for the violation
168 * @param moduleId the id of the module the violation is associated with
169 * @param sourceClass the Class that is the source of the violation
170 * @param customMessage optional custom violation overriding the default
171 * @noinspection ConstructorWithTooManyParameters
172 * @noinspectionreason ConstructorWithTooManyParameters - immutable class requires a large
173 * number of arguments
174 */
175 // -@cs[ParameterNumber] Class is immutable, we need that amount of arguments.
176 public Violation(int lineNo,
177 int columnNo,
178 String bundle,
179 String key,
180 Object[] args,
181 SeverityLevel severityLevel,
182 String moduleId,
183 Class<?> sourceClass,
184 @Nullable String customMessage) {
185 this(lineNo, columnNo, 0, bundle, key, args, severityLevel, moduleId, sourceClass,
186 customMessage);
187 }
188
189 /**
190 * Creates a new {@code Violation} instance.
191 *
192 * @param lineNo line number associated with the violation
193 * @param columnNo column number associated with the violation
194 * @param bundle resource bundle name
195 * @param key the key to locate the translation
196 * @param args arguments for the translation
197 * @param moduleId the id of the module the violation is associated with
198 * @param sourceClass the Class that is the source of the violation
199 * @param customMessage optional custom violation overriding the default
200 * @noinspection ConstructorWithTooManyParameters
201 * @noinspectionreason ConstructorWithTooManyParameters - immutable class requires a large
202 * number of arguments
203 */
204 // -@cs[ParameterNumber] Class is immutable, we need that amount of arguments.
205 public Violation(int lineNo,
206 int columnNo,
207 String bundle,
208 String key,
209 Object[] args,
210 String moduleId,
211 Class<?> sourceClass,
212 @Nullable String customMessage) {
213 this(lineNo,
214 columnNo,
215 bundle,
216 key,
217 args,
218 DEFAULT_SEVERITY,
219 moduleId,
220 sourceClass,
221 customMessage);
222 }
223
224 /**
225 * Creates a new {@code Violation} instance.
226 *
227 * @param lineNo line number associated with the violation
228 * @param bundle resource bundle name
229 * @param key the key to locate the translation
230 * @param args arguments for the translation
231 * @param severityLevel severity level for the violation
232 * @param moduleId the id of the module the violation is associated with
233 * @param sourceClass the source class for the violation
234 * @param customMessage optional custom violation overriding the default
235 * @noinspection ConstructorWithTooManyParameters
236 * @noinspectionreason ConstructorWithTooManyParameters - immutable class requires a large
237 * number of arguments
238 */
239 // -@cs[ParameterNumber] Class is immutable, we need that amount of arguments.
240 public Violation(int lineNo,
241 String bundle,
242 String key,
243 Object[] args,
244 SeverityLevel severityLevel,
245 String moduleId,
246 Class<?> sourceClass,
247 @Nullable String customMessage) {
248 this(lineNo, 0, bundle, key, args, severityLevel, moduleId,
249 sourceClass, customMessage);
250 }
251
252 /**
253 * Creates a new {@code Violation} instance. The column number
254 * defaults to 0.
255 *
256 * @param lineNo line number associated with the violation
257 * @param bundle name of a resource bundle that contains audit event violations
258 * @param key the key to locate the translation
259 * @param args arguments for the translation
260 * @param moduleId the id of the module the violation is associated with
261 * @param sourceClass the name of the source for the violation
262 * @param customMessage optional custom violation overriding the default
263 */
264 public Violation(
265 int lineNo,
266 String bundle,
267 String key,
268 Object[] args,
269 String moduleId,
270 Class<?> sourceClass,
271 @Nullable String customMessage) {
272 this(lineNo, 0, bundle, key, args, DEFAULT_SEVERITY, moduleId,
273 sourceClass, customMessage);
274 }
275
276 /**
277 * Gets the line number.
278 *
279 * @return the line number
280 */
281 public int getLineNo() {
282 return lineNo;
283 }
284
285 /**
286 * Gets the column number.
287 *
288 * @return the column number
289 */
290 public int getColumnNo() {
291 return columnNo;
292 }
293
294 /**
295 * Gets the column char index.
296 *
297 * @return the column char index
298 */
299 public int getColumnCharIndex() {
300 return columnCharIndex;
301 }
302
303 /**
304 * Gets the token type.
305 *
306 * @return the token type
307 */
308 public int getTokenType() {
309 return tokenType;
310 }
311
312 /**
313 * Gets the severity level.
314 *
315 * @return the severity level
316 */
317 public SeverityLevel getSeverityLevel() {
318 return severityLevel;
319 }
320
321 /**
322 * Returns id of module.
323 *
324 * @return the module identifier.
325 */
326 public String getModuleId() {
327 return moduleId;
328 }
329
330 /**
331 * Returns the violation key to locate the translation, can also be used
332 * in IDE plugins to map audit event violations to corrective actions.
333 *
334 * @return the violation key
335 */
336 public String getKey() {
337 return key;
338 }
339
340 /**
341 * Gets the name of the source for this Violation.
342 *
343 * @return the name of the source for this Violation
344 */
345 public String getSourceName() {
346 return sourceClass.getName();
347 }
348
349 /**
350 * Indicates whether some other object is "equal to" this one.
351 * Suppression on enumeration is needed so code stays consistent.
352 *
353 * @noinspection EqualsCalledOnEnumConstant
354 * @noinspectionreason EqualsCalledOnEnumConstant - enumeration is needed to keep
355 * code consistent
356 */
357 // -@cs[CyclomaticComplexity] equals - a lot of fields to check.
358 @Override
359 public boolean equals(Object object) {
360 if (this == object) {
361 return true;
362 }
363 if (object == null || getClass() != object.getClass()) {
364 return false;
365 }
366 final Violation violation = (Violation) object;
367 return Objects.equals(lineNo, violation.lineNo)
368 && Objects.equals(columnNo, violation.columnNo)
369 && Objects.equals(columnCharIndex, violation.columnCharIndex)
370 && Objects.equals(tokenType, violation.tokenType)
371 && Objects.equals(severityLevel, violation.severityLevel)
372 && Objects.equals(moduleId, violation.moduleId)
373 && Objects.equals(key, violation.key)
374 && Objects.equals(bundle, violation.bundle)
375 && Objects.equals(sourceClass, violation.sourceClass)
376 && Objects.equals(customMessage, violation.customMessage)
377 && Arrays.equals(args, violation.args);
378 }
379
380 @Override
381 public int hashCode() {
382 return Objects.hash(lineNo, columnNo, columnCharIndex, tokenType, severityLevel, moduleId,
383 key, bundle, sourceClass, customMessage, Arrays.hashCode(args));
384 }
385
386 ////////////////////////////////////////////////////////////////////////////
387 // Interface Comparable methods
388 ////////////////////////////////////////////////////////////////////////////
389
390 @Override
391 public int compareTo(Violation other) {
392 final int result;
393
394 if (lineNo == other.lineNo) {
395 if (columnNo == other.columnNo) {
396 if (Objects.equals(moduleId, other.moduleId)) {
397 if (Objects.equals(sourceClass, other.sourceClass)) {
398 result = getViolation().compareTo(other.getViolation());
399 }
400 else if (sourceClass == null) {
401 result = -1;
402 }
403 else if (other.sourceClass == null) {
404 result = 1;
405 }
406 else {
407 result = sourceClass.getName().compareTo(other.sourceClass.getName());
408 }
409 }
410 else if (moduleId == null) {
411 result = -1;
412 }
413 else if (other.moduleId == null) {
414 result = 1;
415 }
416 else {
417 result = moduleId.compareTo(other.moduleId);
418 }
419 }
420 else {
421 result = Integer.compare(columnNo, other.columnNo);
422 }
423 }
424 else {
425 result = Integer.compare(lineNo, other.lineNo);
426 }
427 return result;
428 }
429
430 /**
431 * Gets the translated violation.
432 *
433 * @return the translated violation
434 */
435 public String getViolation() {
436 final String violation;
437
438 if (customMessage != null) {
439 violation = new MessageFormat(customMessage, Locale.ROOT).format(args);
440 }
441 else {
442 violation = new LocalizedMessage(bundle, sourceClass, key, args).getMessage();
443 }
444
445 return violation;
446 }
447
448}