001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2025 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018///////////////////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.api;
021
022import java.text.MessageFormat;
023import java.util.Arrays;
024import java.util.Locale;
025import java.util.Objects;
026
027import javax.annotation.Nullable;
028
029import com.puppycrawl.tools.checkstyle.LocalizedMessage;
030import com.puppycrawl.tools.checkstyle.utils.UnmodifiableCollectionUtil;
031
032/**
033 * Represents a violation that can be localised. The translations come from
034 * message.properties files. The underlying implementation uses
035 * java.text.MessageFormat.
036 *
037 * @noinspection ClassWithTooManyConstructors
038 * @noinspectionreason ClassWithTooManyConstructors - immutable nature of class requires a
039 *      bunch of constructors
040 */
041public final class Violation
042    implements Comparable<Violation> {
043
044    /** The default severity level if one is not specified. */
045    private static final SeverityLevel DEFAULT_SEVERITY = SeverityLevel.ERROR;
046
047    /** The line number. **/
048    private final int lineNo;
049    /** The column number. **/
050    private final int columnNo;
051    /** The column char index. **/
052    private final int columnCharIndex;
053    /** The token type constant. See {@link TokenTypes}. **/
054    private final int tokenType;
055
056    /** The severity level. **/
057    private final SeverityLevel severityLevel;
058
059    /** The id of the module generating the violation. */
060    private final String moduleId;
061
062    /** Key for the violation format. **/
063    private final String key;
064
065    /** Arguments for MessageFormat. */
066    private final Object[] args;
067
068    /** Name of the resource bundle to get violations from. **/
069    private final String bundle;
070
071    /** Class of the source for this Violation. */
072    private final Class<?> sourceClass;
073
074    /** A custom violation overriding the default violation from the bundle. */
075    @Nullable
076    private final String customMessage;
077
078    /**
079     * Creates a new {@code Violation} instance.
080     *
081     * @param lineNo line number associated with the violation
082     * @param columnNo column number associated with the violation
083     * @param columnCharIndex column char index associated with the violation
084     * @param tokenType token type of the event associated with violation. See {@link TokenTypes}
085     * @param bundle resource bundle name
086     * @param key the key to locate the translation
087     * @param args arguments for the translation
088     * @param severityLevel severity level for the violation
089     * @param moduleId the id of the module the violation is associated with
090     * @param sourceClass the Class that is the source of the violation
091     * @param customMessage optional custom violation overriding the default
092     * @noinspection ConstructorWithTooManyParameters
093     * @noinspectionreason ConstructorWithTooManyParameters - immutable class requires a large
094     *      number of arguments
095     */
096    // -@cs[ParameterNumber] Class is immutable, we need that amount of arguments.
097    public Violation(int lineNo,
098                            int columnNo,
099                            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}