1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2025 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.
80 *
81 * @param lineNo line number associated with the violation
82 * @param columnNo column number associated with the violation
83 * @param columnCharIndex column char index associated with the violation
84 * @param tokenType token type of the event associated with violation. See {@link TokenTypes}
85 * @param bundle resource bundle name
86 * @param key the key to locate the translation
87 * @param args arguments for the translation
88 * @param severityLevel severity level for the violation
89 * @param moduleId the id of the module the violation is associated with
90 * @param sourceClass the Class that is the source of the violation
91 * @param customMessage optional custom violation overriding the default
92 * @noinspection ConstructorWithTooManyParameters
93 * @noinspectionreason ConstructorWithTooManyParameters - immutable class requires a large
94 * number of arguments
95 */
96 // -@cs[ParameterNumber] Class is immutable, we need that amount of arguments.
97 public Violation(int lineNo,
98 int columnNo,
99 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 }