001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 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. The column number
080     * defaults to 0.
081     *
082     * @param lineNo line number associated with the violation
083     * @param bundle name of a resource bundle that contains audit event violations
084     * @param key the key to locate the translation
085     * @param args arguments for the translation
086     * @param moduleId the id of the module the violation is associated with
087     * @param sourceClass the name of the source for the violation
088     * @param customMessage optional custom violation overriding the default
089     */
090    public Violation(
091        int lineNo,
092        String bundle,
093        String key,
094        Object[] args,
095        String moduleId,
096        Class<?> sourceClass,
097        @Nullable String customMessage) {
098        this(lineNo, 0, bundle, key, args, DEFAULT_SEVERITY, moduleId,
099                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}