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