View Javadoc
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 }