001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 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 com.puppycrawl.tools.checkstyle.LocalizedMessage;
028import com.puppycrawl.tools.checkstyle.utils.UnmodifiableCollectionUtil;
029
030/**
031 * Represents a violation that can be localised. The translations come from
032 * message.properties files. The underlying implementation uses
033 * java.text.MessageFormat.
034 *
035 * @noinspection ClassWithTooManyConstructors
036 * @noinspectionreason ClassWithTooManyConstructors - immutable nature of class requires a
037 *      bunch of constructors
038 */
039public final class Violation
040    implements Comparable<Violation> {
041
042    /** The default severity level if one is not specified. */
043    private static final SeverityLevel DEFAULT_SEVERITY = SeverityLevel.ERROR;
044
045    /** The line number. **/
046    private final int lineNo;
047    /** The column number. **/
048    private final int columnNo;
049    /** The column char index. **/
050    private final int columnCharIndex;
051    /** The token type constant. See {@link TokenTypes}. **/
052    private final int tokenType;
053
054    /** The severity level. **/
055    private final SeverityLevel severityLevel;
056
057    /** The id of the module generating the violation. */
058    private final String moduleId;
059
060    /** Key for the violation format. **/
061    private final String key;
062
063    /** Arguments for MessageFormat. */
064    private final Object[] args;
065
066    /** Name of the resource bundle to get violations from. **/
067    private final String bundle;
068
069    /** Class of the source for this Violation. */
070    private final Class<?> sourceClass;
071
072    /** A custom violation overriding the default violation from the bundle. */
073    private final String customMessage;
074
075    /**
076     * Creates a new {@code Violation} instance.
077     *
078     * @param lineNo line number associated with the violation
079     * @param columnNo column number associated with the violation
080     * @param columnCharIndex column char index associated with the violation
081     * @param tokenType token type of the event associated with violation. See {@link TokenTypes}
082     * @param bundle resource bundle name
083     * @param key the key to locate the translation
084     * @param args arguments for the translation
085     * @param severityLevel severity level for the violation
086     * @param moduleId the id of the module the violation is associated with
087     * @param sourceClass the Class that is the source of the violation
088     * @param customMessage optional custom violation overriding the default
089     * @noinspection ConstructorWithTooManyParameters
090     * @noinspectionreason ConstructorWithTooManyParameters - immutable class requires a large
091     *      number of arguments
092     */
093    // -@cs[ParameterNumber] Class is immutable, we need that amount of arguments.
094    public Violation(int lineNo,
095                            int columnNo,
096                            int columnCharIndex,
097                            int tokenType,
098                            String bundle,
099                            String key,
100                            Object[] args,
101                            SeverityLevel severityLevel,
102                            String moduleId,
103                            Class<?> sourceClass,
104                            String customMessage) {
105        this.lineNo = lineNo;
106        this.columnNo = columnNo;
107        this.columnCharIndex = columnCharIndex;
108        this.tokenType = tokenType;
109        this.key = key;
110
111        if (args == null) {
112            this.args = null;
113        }
114        else {
115            this.args = UnmodifiableCollectionUtil.copyOfArray(args, args.length);
116        }
117        this.bundle = bundle;
118        this.severityLevel = severityLevel;
119        this.moduleId = moduleId;
120        this.sourceClass = sourceClass;
121        this.customMessage = customMessage;
122    }
123
124    /**
125     * Creates a new {@code Violation} instance.
126     *
127     * @param lineNo line number associated with the violation
128     * @param columnNo column number associated with the violation
129     * @param tokenType token type of the event associated with violation. See {@link TokenTypes}
130     * @param bundle resource bundle name
131     * @param key the key to locate the translation
132     * @param args arguments for the translation
133     * @param severityLevel severity level for the violation
134     * @param moduleId the id of the module the violation is associated with
135     * @param sourceClass the Class that is the source of the violation
136     * @param customMessage optional custom violation overriding the default
137     * @noinspection ConstructorWithTooManyParameters
138     * @noinspectionreason ConstructorWithTooManyParameters - immutable class requires a large
139     *      number of arguments
140     */
141    // -@cs[ParameterNumber] Class is immutable, we need that amount of arguments.
142    public Violation(int lineNo,
143                            int columnNo,
144                            int tokenType,
145                            String bundle,
146                            String key,
147                            Object[] args,
148                            SeverityLevel severityLevel,
149                            String moduleId,
150                            Class<?> sourceClass,
151                            String customMessage) {
152        this(lineNo, columnNo, columnNo, tokenType, bundle, key, args, severityLevel, moduleId,
153                sourceClass, customMessage);
154    }
155
156    /**
157     * Creates a new {@code Violation} instance.
158     *
159     * @param lineNo line number associated with the violation
160     * @param columnNo column number associated with the violation
161     * @param bundle resource bundle name
162     * @param key the key to locate the translation
163     * @param args arguments for the translation
164     * @param severityLevel severity level for the violation
165     * @param moduleId the id of the module the violation is associated with
166     * @param sourceClass the Class that is the source of the violation
167     * @param customMessage optional custom violation overriding the default
168     * @noinspection ConstructorWithTooManyParameters
169     * @noinspectionreason ConstructorWithTooManyParameters - immutable class requires a large
170     *      number of arguments
171     */
172    // -@cs[ParameterNumber] Class is immutable, we need that amount of arguments.
173    public Violation(int lineNo,
174                            int columnNo,
175                            String bundle,
176                            String key,
177                            Object[] args,
178                            SeverityLevel severityLevel,
179                            String moduleId,
180                            Class<?> sourceClass,
181                            String customMessage) {
182        this(lineNo, columnNo, 0, bundle, key, args, severityLevel, moduleId, sourceClass,
183                customMessage);
184    }
185
186    /**
187     * Creates a new {@code Violation} instance.
188     *
189     * @param lineNo line number associated with the violation
190     * @param columnNo column number associated with the violation
191     * @param bundle resource bundle name
192     * @param key the key to locate the translation
193     * @param args arguments for the translation
194     * @param moduleId the id of the module the violation is associated with
195     * @param sourceClass the Class that is the source of the violation
196     * @param customMessage optional custom violation overriding the default
197     * @noinspection ConstructorWithTooManyParameters
198     * @noinspectionreason ConstructorWithTooManyParameters - immutable class requires a large
199     *      number of arguments
200     */
201    // -@cs[ParameterNumber] Class is immutable, we need that amount of arguments.
202    public Violation(int lineNo,
203                            int columnNo,
204                            String bundle,
205                            String key,
206                            Object[] args,
207                            String moduleId,
208                            Class<?> sourceClass,
209                            String customMessage) {
210        this(lineNo,
211                columnNo,
212             bundle,
213             key,
214             args,
215             DEFAULT_SEVERITY,
216             moduleId,
217             sourceClass,
218             customMessage);
219    }
220
221    /**
222     * Creates a new {@code Violation} instance.
223     *
224     * @param lineNo line number associated with the violation
225     * @param bundle resource bundle name
226     * @param key the key to locate the translation
227     * @param args arguments for the translation
228     * @param severityLevel severity level for the violation
229     * @param moduleId the id of the module the violation is associated with
230     * @param sourceClass the source class for the violation
231     * @param customMessage optional custom violation overriding the default
232     * @noinspection ConstructorWithTooManyParameters
233     * @noinspectionreason ConstructorWithTooManyParameters - immutable class requires a large
234     *      number of arguments
235     */
236    // -@cs[ParameterNumber] Class is immutable, we need that amount of arguments.
237    public Violation(int lineNo,
238                            String bundle,
239                            String key,
240                            Object[] args,
241                            SeverityLevel severityLevel,
242                            String moduleId,
243                            Class<?> sourceClass,
244                            String customMessage) {
245        this(lineNo, 0, bundle, key, args, severityLevel, moduleId,
246                sourceClass, customMessage);
247    }
248
249    /**
250     * Creates a new {@code Violation} instance. The column number
251     * defaults to 0.
252     *
253     * @param lineNo line number associated with the violation
254     * @param bundle name of a resource bundle that contains audit event violations
255     * @param key the key to locate the translation
256     * @param args arguments for the translation
257     * @param moduleId the id of the module the violation is associated with
258     * @param sourceClass the name of the source for the violation
259     * @param customMessage optional custom violation overriding the default
260     */
261    public Violation(
262        int lineNo,
263        String bundle,
264        String key,
265        Object[] args,
266        String moduleId,
267        Class<?> sourceClass,
268        String customMessage) {
269        this(lineNo, 0, bundle, key, args, DEFAULT_SEVERITY, moduleId,
270                sourceClass, 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    public boolean equals(Object object) {
357        if (this == object) {
358            return true;
359        }
360        if (object == null || getClass() != object.getClass()) {
361            return false;
362        }
363        final Violation violation = (Violation) object;
364        return Objects.equals(lineNo, violation.lineNo)
365                && Objects.equals(columnNo, violation.columnNo)
366                && Objects.equals(columnCharIndex, violation.columnCharIndex)
367                && Objects.equals(tokenType, violation.tokenType)
368                && Objects.equals(severityLevel, violation.severityLevel)
369                && Objects.equals(moduleId, violation.moduleId)
370                && Objects.equals(key, violation.key)
371                && Objects.equals(bundle, violation.bundle)
372                && Objects.equals(sourceClass, violation.sourceClass)
373                && Objects.equals(customMessage, violation.customMessage)
374                && Arrays.equals(args, violation.args);
375    }
376
377    @Override
378    public int hashCode() {
379        return Objects.hash(lineNo, columnNo, columnCharIndex, tokenType, severityLevel, moduleId,
380                key, bundle, sourceClass, customMessage, Arrays.hashCode(args));
381    }
382
383    ////////////////////////////////////////////////////////////////////////////
384    // Interface Comparable methods
385    ////////////////////////////////////////////////////////////////////////////
386
387    @Override
388    public int compareTo(Violation other) {
389        final int result;
390
391        if (lineNo == other.lineNo) {
392            if (columnNo == other.columnNo) {
393                if (Objects.equals(moduleId, other.moduleId)) {
394                    if (Objects.equals(sourceClass, other.sourceClass)) {
395                        result = getViolation().compareTo(other.getViolation());
396                    }
397                    else if (sourceClass == null) {
398                        result = -1;
399                    }
400                    else if (other.sourceClass == null) {
401                        result = 1;
402                    }
403                    else {
404                        result = sourceClass.getName().compareTo(other.sourceClass.getName());
405                    }
406                }
407                else if (moduleId == null) {
408                    result = -1;
409                }
410                else if (other.moduleId == null) {
411                    result = 1;
412                }
413                else {
414                    result = moduleId.compareTo(other.moduleId);
415                }
416            }
417            else {
418                result = Integer.compare(columnNo, other.columnNo);
419            }
420        }
421        else {
422            result = Integer.compare(lineNo, other.lineNo);
423        }
424        return result;
425    }
426
427    /**
428     * Gets the translated violation.
429     *
430     * @return the translated violation
431     */
432    public String getViolation() {
433        final String violation;
434
435        if (customMessage != null) {
436            violation = new MessageFormat(customMessage, Locale.ROOT).format(args);
437        }
438        else {
439            violation = new LocalizedMessage(bundle, sourceClass, key, args).getMessage();
440        }
441
442        return violation;
443    }
444
445}