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 java.text.MessageFormat.
35   *
36   * @noinspection ClassWithTooManyConstructors
37   * @noinspectionreason ClassWithTooManyConstructors - immutable nature of class requires a
38   *      bunch of constructors
39   */
40  public final class Violation implements Comparable<Violation> {
41  
42      /** The default severity level if one is not specified. */
43      private static final SeverityLevel DEFAULT_SEVERITY = SeverityLevel.ERROR;
44  
45      /** The line number. */
46      private final int lineNo;
47      /** The column number. */
48      private final int columnNo;
49      /** The column char index. */
50      private final int columnCharIndex;
51      /** The token type constant. See {@link TokenTypes}. */
52      private final int tokenType;
53  
54      /** The severity level. */
55      private final SeverityLevel severityLevel;
56  
57      /** The id of the module generating the violation. */
58      private final String moduleId;
59  
60      /** Key for the violation format. */
61      private final String key;
62  
63      /** Arguments for MessageFormat. */
64      private final Object[] args;
65  
66      /** Name of the resource bundle to get violations from. */
67      private final String bundle;
68  
69      /** Class of the source for this Violation. */
70      private final Class<?> sourceClass;
71  
72      /** A custom violation overriding the default violation from the bundle. */
73      @Nullable
74      private final String customMessage;
75  
76      /**
77       * Creates a new {@code Violation} instance. The column number defaults to 0.
78       *
79       * @param lineNo line number associated with the violation
80       * @param bundle name of a resource bundle that contains audit event violations
81       * @param key the key to locate the translation
82       * @param args arguments for the translation
83       * @param moduleId the id of the module the violation is associated with
84       * @param sourceClass the name of the source for the violation
85       * @param customMessage optional custom violation overriding the default
86       */
87      public Violation(
88          int lineNo,
89          String bundle,
90          String key,
91          Object[] args,
92          String moduleId,
93          Class<?> sourceClass,
94          @Nullable String customMessage) {
95          this(lineNo, 0, bundle, key, args, DEFAULT_SEVERITY, moduleId,
96                  sourceClass, customMessage);
97      }
98  
99      /**
100      * Creates a new {@code Violation} instance.
101      *
102      * @param lineNo line number associated with the violation
103      * @param columnNo column number associated with the violation
104      * @param bundle resource bundle name
105      * @param key the key to locate the translation
106      * @param args arguments for the translation
107      * @param moduleId the id of the module the violation is associated with
108      * @param sourceClass the Class that is the source of the violation
109      * @param customMessage optional custom violation overriding the default
110      * @noinspection ConstructorWithTooManyParameters
111      * @noinspectionreason ConstructorWithTooManyParameters - immutable class requires a large
112      *      number of arguments
113      */
114     // -@cs[ParameterNumber] Class is immutable, we need that amount of arguments.
115     public Violation(int lineNo,
116                             int columnNo,
117                             String bundle,
118                             String key,
119                             Object[] args,
120                             String moduleId,
121                             Class<?> sourceClass,
122                             @Nullable String customMessage) {
123         this(lineNo,
124                 columnNo,
125              bundle,
126              key,
127              args,
128              DEFAULT_SEVERITY,
129              moduleId,
130              sourceClass,
131              customMessage);
132     }
133 
134     /**
135      * Creates a new {@code Violation} instance.
136      *
137      * @param lineNo line number associated with the violation
138      * @param bundle resource bundle name
139      * @param key the key to locate the translation
140      * @param args arguments for the translation
141      * @param severityLevel severity level for the violation
142      * @param moduleId the id of the module the violation is associated with
143      * @param sourceClass the source class for the violation
144      * @param customMessage optional custom violation overriding the default
145      * @noinspection ConstructorWithTooManyParameters
146      * @noinspectionreason ConstructorWithTooManyParameters - immutable class requires a large
147      *      number of arguments
148      */
149     // -@cs[ParameterNumber] Class is immutable, we need that amount of arguments.
150     public Violation(int lineNo,
151                             String bundle,
152                             String key,
153                             Object[] args,
154                             SeverityLevel severityLevel,
155                             String moduleId,
156                             Class<?> sourceClass,
157                             @Nullable String customMessage) {
158         this(lineNo, 0, bundle, key, args, severityLevel, moduleId,
159                 sourceClass, customMessage);
160     }
161 
162     /**
163      * Creates a new {@code Violation} instance.
164      *
165      * @param lineNo line number associated with the violation
166      * @param columnNo column number associated with the violation
167      * @param bundle resource bundle name
168      * @param key the key to locate the translation
169      * @param args arguments for the translation
170      * @param severityLevel severity level for the violation
171      * @param moduleId the id of the module the violation is associated with
172      * @param sourceClass the Class that is the source of the violation
173      * @param customMessage optional custom violation overriding the default
174      * @noinspection ConstructorWithTooManyParameters
175      * @noinspectionreason ConstructorWithTooManyParameters - immutable class requires a large
176      *      number of arguments
177      */
178     // -@cs[ParameterNumber] Class is immutable, we need that amount of arguments.
179     public Violation(int lineNo,
180                             int columnNo,
181                             String bundle,
182                             String key,
183                             Object[] args,
184                             SeverityLevel severityLevel,
185                             String moduleId,
186                             Class<?> sourceClass,
187                             @Nullable String customMessage) {
188         this(lineNo, columnNo, 0, bundle, key, args, severityLevel, moduleId, sourceClass,
189                 customMessage);
190     }
191 
192     /**
193      * Creates a new {@code Violation} instance.
194      *
195      * @param lineNo line number associated with the violation
196      * @param columnNo column number associated with the violation
197      * @param tokenType token type of the event associated with violation. See {@link TokenTypes}
198      * @param bundle resource bundle name
199      * @param key the key to locate the translation
200      * @param args arguments for the translation
201      * @param severityLevel severity level for the violation
202      * @param moduleId the id of the module the violation is associated with
203      * @param sourceClass the Class that is the source of the violation
204      * @param customMessage optional custom violation overriding the default
205      * @noinspection ConstructorWithTooManyParameters
206      * @noinspectionreason ConstructorWithTooManyParameters - immutable class requires a large
207      *      number of arguments
208      */
209     // -@cs[ParameterNumber] Class is immutable, we need that amount of arguments.
210     public Violation(int lineNo,
211                             int columnNo,
212                             int tokenType,
213                             String bundle,
214                             String key,
215                             Object[] args,
216                             SeverityLevel severityLevel,
217                             String moduleId,
218                             Class<?> sourceClass,
219                             @Nullable String customMessage) {
220         this(lineNo, columnNo, columnNo, tokenType, bundle, key, args, severityLevel, moduleId,
221                 sourceClass, customMessage);
222     }
223 
224     /**
225      * Creates a new {@code Violation} instance.
226      *
227      * @param lineNo line number associated with the violation
228      * @param columnNo column number associated with the violation
229      * @param columnCharIndex column char index associated with the violation
230      * @param tokenType token type of the event associated with violation. See {@link TokenTypes}
231      * @param bundle resource bundle name
232      * @param key the key to locate the translation
233      * @param args arguments for the translation
234      * @param severityLevel severity level for the violation
235      * @param moduleId the id of the module the violation is associated with
236      * @param sourceClass the Class that is the source of the violation
237      * @param customMessage optional custom violation overriding the default
238      * @noinspection ConstructorWithTooManyParameters
239      * @noinspectionreason ConstructorWithTooManyParameters - immutable class requires a large
240      *      number of arguments
241      */
242     // -@cs[ParameterNumber] Class is immutable, we need that amount of arguments.
243     public Violation(int lineNo,
244                             int columnNo,
245                             int columnCharIndex,
246                             int tokenType,
247                             String bundle,
248                             String key,
249                             Object[] args,
250                             SeverityLevel severityLevel,
251                             String moduleId,
252                             Class<?> sourceClass,
253                             @Nullable String customMessage) {
254         this.lineNo = lineNo;
255         this.columnNo = columnNo;
256         this.columnCharIndex = columnCharIndex;
257         this.tokenType = tokenType;
258         this.key = key;
259 
260         if (args == null) {
261             this.args = null;
262         }
263         else {
264             this.args = UnmodifiableCollectionUtil.copyOfArray(args, args.length);
265         }
266         this.bundle = bundle;
267         this.severityLevel = severityLevel;
268         this.moduleId = moduleId;
269         this.sourceClass = sourceClass;
270         this.customMessage = customMessage;
271     }
272 
273     /**
274      * Gets the line number.
275      *
276      * @return the line number
277      */
278     public int getLineNo() {
279         return lineNo;
280     }
281 
282     /**
283      * Gets the column number.
284      *
285      * @return the column number
286      */
287     public int getColumnNo() {
288         return columnNo;
289     }
290 
291     /**
292      * Gets the column char index.
293      *
294      * @return the column char index
295      */
296     public int getColumnCharIndex() {
297         return columnCharIndex;
298     }
299 
300     /**
301      * Gets the token type.
302      *
303      * @return the token type
304      */
305     public int getTokenType() {
306         return tokenType;
307     }
308 
309     /**
310      * Gets the severity level.
311      *
312      * @return the severity level
313      */
314     public SeverityLevel getSeverityLevel() {
315         return severityLevel;
316     }
317 
318     /**
319      * Returns id of module.
320      *
321      * @return the module identifier.
322      */
323     public String getModuleId() {
324         return moduleId;
325     }
326 
327     /**
328      * Returns the violation key to locate the translation, can also be used
329      * in IDE plugins to map audit event violations to corrective actions.
330      *
331      * @return the violation key
332      */
333     public String getKey() {
334         return key;
335     }
336 
337     /**
338      * Gets the name of the source for this Violation.
339      *
340      * @return the name of the source for this Violation
341      */
342     public String getSourceName() {
343         return sourceClass.getName();
344     }
345 
346     /**
347      * Indicates whether some other object is "equal to" this one.
348      * Suppression on enumeration is needed so code stays consistent.
349      *
350      * @noinspection EqualsCalledOnEnumConstant
351      * @noinspectionreason EqualsCalledOnEnumConstant - enumeration is needed to keep
352      *      code consistent
353      */
354     // -@cs[CyclomaticComplexity] equals - a lot of fields to check.
355     @Override
356     @SuppressWarnings("OverlyComplexBooleanExpression")
357     public boolean equals(Object object) {
358         if (this == object) {
359             return true;
360         }
361         if (object == null || getClass() != object.getClass()) {
362             return false;
363         }
364         final Violation violation = (Violation) object;
365         return lineNo == violation.lineNo
366                 && columnNo == violation.columnNo
367                 && columnCharIndex == violation.columnCharIndex
368                 && tokenType == violation.tokenType
369                 && Objects.equals(severityLevel, violation.severityLevel)
370                 && Objects.equals(moduleId, violation.moduleId)
371                 && Objects.equals(key, violation.key)
372                 && Objects.equals(bundle, violation.bundle)
373                 && Objects.equals(sourceClass, violation.sourceClass)
374                 && Objects.equals(customMessage, violation.customMessage)
375                 && Arrays.equals(args, violation.args);
376     }
377 
378     @Override
379     public int hashCode() {
380         return Objects.hash(lineNo, columnNo, columnCharIndex, tokenType, severityLevel, moduleId,
381                 key, bundle, sourceClass, customMessage, Arrays.hashCode(args));
382     }
383 
384     ////////////////////////////////////////////////////////////////////////////
385     // Interface Comparable methods
386     ////////////////////////////////////////////////////////////////////////////
387 
388     /**
389      * {@inheritDoc}
390      *
391      * <p>The same fields as in {@link #equals(Object)} take part in the
392      * comparison, so that a result of zero is consistent with equality, as the
393      * {@link Comparable} contract recommends. Line, column, module id, source
394      * class, and the rendered violation stay the leading criteria; the
395      * remaining fields only break a tie between violations that would
396      * otherwise be reported at the same place with the same text.
397      */
398     @Override
399     public int compareTo(Violation other) {
400         int result = compareLocation(other);
401         if (result == 0) {
402             result = compareContent(other);
403         }
404         return result;
405     }
406 
407     /**
408      * Compares the place and the text a violation is reported with.
409      *
410      * @param other the violation to compare against
411      * @return the value {@code 0} if the violations are reported the same way
412      */
413     private int compareLocation(Violation other) {
414         int result = Integer.compare(lineNo, other.lineNo);
415         if (result == 0) {
416             result = Integer.compare(columnNo, other.columnNo);
417         }
418         if (result == 0) {
419             result = compareNullable(moduleId, other.moduleId);
420         }
421         if (result == 0) {
422             result = compareNullable(getSourceClassName(), other.getSourceClassName());
423         }
424         if (result == 0) {
425             result = getViolation().compareTo(other.getViolation());
426         }
427         return result;
428     }
429 
430     /**
431      * Compares the fields that two violations reported at the same place with
432      * the same text can still differ in.
433      *
434      * @param other the violation to compare against
435      * @return the value {@code 0} if the violations carry the same content
436      */
437     private int compareContent(Violation other) {
438         int result = Integer.compare(columnCharIndex, other.columnCharIndex);
439         if (result == 0) {
440             result = Integer.compare(tokenType, other.tokenType);
441         }
442         if (result == 0) {
443             result = severityLevel.compareTo(other.severityLevel);
444         }
445         if (result == 0) {
446             result = compareNullable(key, other.key);
447         }
448         if (result == 0) {
449             result = compareNullable(bundle, other.bundle);
450         }
451         if (result == 0) {
452             result = compareNullable(customMessage, other.customMessage);
453         }
454         if (result == 0) {
455             result = Arrays.deepToString(args).compareTo(Arrays.deepToString(other.args));
456         }
457         return result;
458     }
459 
460     /**
461      * Compares two values that may be {@code null}, ordering {@code null} before any other value.
462      *
463      * @param first the first value
464      * @param second the second value
465      * @return a negative integer, zero, or a positive integer as the first
466      *     value is less than, equal to, or greater than the second
467      */
468     private static int compareNullable(@Nullable String first, @Nullable String second) {
469         final int result;
470         if (first == null) {
471             if (second == null) {
472                 result = 0;
473             }
474             else {
475                 result = -1;
476             }
477         }
478         else if (second == null) {
479             result = 1;
480         }
481         else {
482             result = first.compareTo(second);
483         }
484         return result;
485     }
486 
487     /**
488      * Gets the source class name or {@code null} when no source class is set.
489      *
490      * @return the source class name or {@code null}
491      */
492     @Nullable
493     private String getSourceClassName() {
494         final String name;
495         if (sourceClass == null) {
496             name = null;
497         }
498         else {
499             name = sourceClass.getName();
500         }
501         return name;
502     }
503 
504     /**
505      * Gets the translated violation.
506      *
507      * @return the translated violation
508      */
509     public String getViolation() {
510         final String violation;
511 
512         if (customMessage != null) {
513             violation = new MessageFormat(customMessage, Locale.ROOT).format(args);
514         }
515         else {
516             violation = new LocalizedMessage(bundle, sourceClass, key, args).getMessage();
517         }
518 
519         return violation;
520     }
521 
522 }