1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2026 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.text.MessageFormat;
23 import java.util.Arrays;
24 import java.util.Locale;
25 import java.util.Objects;
26
27 import javax.annotation.Nullable;
28
29 import com.puppycrawl.tools.checkstyle.LocalizedMessage;
30 import com.puppycrawl.tools.checkstyle.utils.UnmodifiableCollectionUtil;
31
32 /**
33 * Represents a violation that can be localised. The translations come from
34 * message.properties files. The underlying implementation uses
35 * java.text.MessageFormat.
36 *
37 * @noinspection ClassWithTooManyConstructors
38 * @noinspectionreason ClassWithTooManyConstructors - immutable nature of class requires a
39 * bunch of constructors
40 */
41 public final class Violation
42 implements Comparable<Violation> {
43
44 /** The default severity level if one is not specified. */
45 private static final SeverityLevel DEFAULT_SEVERITY = SeverityLevel.ERROR;
46
47 /** The line number. **/
48 private final int lineNo;
49 /** The column number. **/
50 private final int columnNo;
51 /** The column char index. **/
52 private final int columnCharIndex;
53 /** The token type constant. See {@link TokenTypes}. **/
54 private final int tokenType;
55
56 /** The severity level. **/
57 private final SeverityLevel severityLevel;
58
59 /** The id of the module generating the violation. */
60 private final String moduleId;
61
62 /** Key for the violation format. **/
63 private final String key;
64
65 /** Arguments for MessageFormat. */
66 private final Object[] args;
67
68 /** Name of the resource bundle to get violations from. **/
69 private final String bundle;
70
71 /** Class of the source for this Violation. */
72 private final Class<?> sourceClass;
73
74 /** A custom violation overriding the default violation from the bundle. */
75 @Nullable
76 private final String customMessage;
77
78 /**
79 * Creates a new {@code Violation} instance. The column number
80 * defaults to 0.
81 *
82 * @param lineNo line number associated with the violation
83 * @param bundle name of a resource bundle that contains audit event violations
84 * @param key the key to locate the translation
85 * @param args arguments for the translation
86 * @param moduleId the id of the module the violation is associated with
87 * @param sourceClass the name of the source for the violation
88 * @param customMessage optional custom violation overriding the default
89 */
90 public Violation(
91 int lineNo,
92 String bundle,
93 String key,
94 Object[] args,
95 String moduleId,
96 Class<?> sourceClass,
97 @Nullable String customMessage) {
98 this(lineNo, 0, bundle, key, args, DEFAULT_SEVERITY, moduleId,
99 sourceClass, customMessage);
100 }
101
102 /**
103 * Creates a new {@code Violation} instance.
104 *
105 * @param lineNo line number associated with the violation
106 * @param columnNo column number associated with the violation
107 * @param bundle resource bundle name
108 * @param key the key to locate the translation
109 * @param args arguments for the translation
110 * @param moduleId the id of the module the violation is associated with
111 * @param sourceClass the Class that is the source of the violation
112 * @param customMessage optional custom violation overriding the default
113 * @noinspection ConstructorWithTooManyParameters
114 * @noinspectionreason ConstructorWithTooManyParameters - immutable class requires a large
115 * number of arguments
116 */
117 // -@cs[ParameterNumber] Class is immutable, we need that amount of arguments.
118 public Violation(int lineNo,
119 int columnNo,
120 String bundle,
121 String key,
122 Object[] args,
123 String moduleId,
124 Class<?> sourceClass,
125 @Nullable String customMessage) {
126 this(lineNo,
127 columnNo,
128 bundle,
129 key,
130 args,
131 DEFAULT_SEVERITY,
132 moduleId,
133 sourceClass,
134 customMessage);
135 }
136
137 /**
138 * Creates a new {@code Violation} instance.
139 *
140 * @param lineNo line number associated with the violation
141 * @param bundle resource bundle name
142 * @param key the key to locate the translation
143 * @param args arguments for the translation
144 * @param severityLevel severity level for the violation
145 * @param moduleId the id of the module the violation is associated with
146 * @param sourceClass the source class for the violation
147 * @param customMessage optional custom violation overriding the default
148 * @noinspection ConstructorWithTooManyParameters
149 * @noinspectionreason ConstructorWithTooManyParameters - immutable class requires a large
150 * number of arguments
151 */
152 // -@cs[ParameterNumber] Class is immutable, we need that amount of arguments.
153 public Violation(int lineNo,
154 String bundle,
155 String key,
156 Object[] args,
157 SeverityLevel severityLevel,
158 String moduleId,
159 Class<?> sourceClass,
160 @Nullable String customMessage) {
161 this(lineNo, 0, bundle, key, args, severityLevel, moduleId,
162 sourceClass, customMessage);
163 }
164
165 /**
166 * Creates a new {@code Violation} instance.
167 *
168 * @param lineNo line number associated with the violation
169 * @param columnNo column number associated with the violation
170 * @param bundle resource bundle name
171 * @param key the key to locate the translation
172 * @param args arguments for the translation
173 * @param severityLevel severity level for the violation
174 * @param moduleId the id of the module the violation is associated with
175 * @param sourceClass the Class that is the source of the violation
176 * @param customMessage optional custom violation overriding the default
177 * @noinspection ConstructorWithTooManyParameters
178 * @noinspectionreason ConstructorWithTooManyParameters - immutable class requires a large
179 * number of arguments
180 */
181 // -@cs[ParameterNumber] Class is immutable, we need that amount of arguments.
182 public Violation(int lineNo,
183 int columnNo,
184 String bundle,
185 String key,
186 Object[] args,
187 SeverityLevel severityLevel,
188 String moduleId,
189 Class<?> sourceClass,
190 @Nullable String customMessage) {
191 this(lineNo, columnNo, 0, bundle, key, args, severityLevel, moduleId, sourceClass,
192 customMessage);
193 }
194
195 /**
196 * Creates a new {@code Violation} instance.
197 *
198 * @param lineNo line number associated with the violation
199 * @param columnNo column number associated with the violation
200 * @param tokenType token type of the event associated with violation. See {@link TokenTypes}
201 * @param bundle resource bundle name
202 * @param key the key to locate the translation
203 * @param args arguments for the translation
204 * @param severityLevel severity level for the violation
205 * @param moduleId the id of the module the violation is associated with
206 * @param sourceClass the Class that is the source of the violation
207 * @param customMessage optional custom violation overriding the default
208 * @noinspection ConstructorWithTooManyParameters
209 * @noinspectionreason ConstructorWithTooManyParameters - immutable class requires a large
210 * number of arguments
211 */
212 // -@cs[ParameterNumber] Class is immutable, we need that amount of arguments.
213 public Violation(int lineNo,
214 int columnNo,
215 int tokenType,
216 String bundle,
217 String key,
218 Object[] args,
219 SeverityLevel severityLevel,
220 String moduleId,
221 Class<?> sourceClass,
222 @Nullable String customMessage) {
223 this(lineNo, columnNo, columnNo, tokenType, bundle, key, args, severityLevel, moduleId,
224 sourceClass, customMessage);
225 }
226
227 /**
228 * Creates a new {@code Violation} instance.
229 *
230 * @param lineNo line number associated with the violation
231 * @param columnNo column number associated with the violation
232 * @param columnCharIndex column char index associated with the violation
233 * @param tokenType token type of the event associated with violation. See {@link TokenTypes}
234 * @param bundle resource bundle name
235 * @param key the key to locate the translation
236 * @param args arguments for the translation
237 * @param severityLevel severity level for the violation
238 * @param moduleId the id of the module the violation is associated with
239 * @param sourceClass the Class that is the source of the violation
240 * @param customMessage optional custom violation overriding the default
241 * @noinspection ConstructorWithTooManyParameters
242 * @noinspectionreason ConstructorWithTooManyParameters - immutable class requires a large
243 * number of arguments
244 */
245 // -@cs[ParameterNumber] Class is immutable, we need that amount of arguments.
246 public Violation(int lineNo,
247 int columnNo,
248 int columnCharIndex,
249 int tokenType,
250 String bundle,
251 String key,
252 Object[] args,
253 SeverityLevel severityLevel,
254 String moduleId,
255 Class<?> sourceClass,
256 @Nullable String customMessage) {
257 this.lineNo = lineNo;
258 this.columnNo = columnNo;
259 this.columnCharIndex = columnCharIndex;
260 this.tokenType = tokenType;
261 this.key = key;
262
263 if (args == null) {
264 this.args = null;
265 }
266 else {
267 this.args = UnmodifiableCollectionUtil.copyOfArray(args, args.length);
268 }
269 this.bundle = bundle;
270 this.severityLevel = severityLevel;
271 this.moduleId = moduleId;
272 this.sourceClass = sourceClass;
273 this.customMessage = 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 @SuppressWarnings("OverlyComplexBooleanExpression")
360 public boolean equals(Object object) {
361 if (this == object) {
362 return true;
363 }
364 if (object == null || getClass() != object.getClass()) {
365 return false;
366 }
367 final Violation violation = (Violation) object;
368 return lineNo == violation.lineNo
369 && columnNo == violation.columnNo
370 && columnCharIndex == violation.columnCharIndex
371 && tokenType == violation.tokenType
372 && Objects.equals(severityLevel, violation.severityLevel)
373 && Objects.equals(moduleId, violation.moduleId)
374 && Objects.equals(key, violation.key)
375 && Objects.equals(bundle, violation.bundle)
376 && Objects.equals(sourceClass, violation.sourceClass)
377 && Objects.equals(customMessage, violation.customMessage)
378 && Arrays.equals(args, violation.args);
379 }
380
381 @Override
382 public int hashCode() {
383 return Objects.hash(lineNo, columnNo, columnCharIndex, tokenType, severityLevel, moduleId,
384 key, bundle, sourceClass, customMessage, Arrays.hashCode(args));
385 }
386
387 ////////////////////////////////////////////////////////////////////////////
388 // Interface Comparable methods
389 ////////////////////////////////////////////////////////////////////////////
390
391 @Override
392 public int compareTo(Violation other) {
393 final int result;
394
395 if (lineNo == other.lineNo) {
396 if (columnNo == other.columnNo) {
397 if (Objects.equals(moduleId, other.moduleId)) {
398 if (Objects.equals(sourceClass, other.sourceClass)) {
399 result = getViolation().compareTo(other.getViolation());
400 }
401 else if (sourceClass == null) {
402 result = -1;
403 }
404 else if (other.sourceClass == null) {
405 result = 1;
406 }
407 else {
408 result = sourceClass.getName().compareTo(other.sourceClass.getName());
409 }
410 }
411 else if (moduleId == null) {
412 result = -1;
413 }
414 else if (other.moduleId == null) {
415 result = 1;
416 }
417 else {
418 result = moduleId.compareTo(other.moduleId);
419 }
420 }
421 else {
422 result = Integer.compare(columnNo, other.columnNo);
423 }
424 }
425 else {
426 result = Integer.compare(lineNo, other.lineNo);
427 }
428 return result;
429 }
430
431 /**
432 * Gets the translated violation.
433 *
434 * @return the translated violation
435 */
436 public String getViolation() {
437 final String violation;
438
439 if (customMessage != null) {
440 violation = new MessageFormat(customMessage, Locale.ROOT).format(args);
441 }
442 else {
443 violation = new LocalizedMessage(bundle, sourceClass, key, args).getMessage();
444 }
445
446 return violation;
447 }
448
449 }