001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2023 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;
028
029/**
030 * Represents a violation that can be localised. The translations come from
031 * message.properties files. The underlying implementation uses
032 * java.text.MessageFormat.
033 *
034 * @noinspection ClassWithTooManyConstructors
035 * @noinspectionreason ClassWithTooManyConstructors - immutable nature of class requires a
036 *      bunch of constructors
037 */
038public final class Violation
039    implements Comparable<Violation> {
040
041    /** The default severity level if one is not specified. */
042    private static final SeverityLevel DEFAULT_SEVERITY = SeverityLevel.ERROR;
043
044    /** The line number. **/
045    private final int lineNo;
046    /** The column number. **/
047    private final int columnNo;
048    /** The column char index. **/
049    private final int columnCharIndex;
050    /** The token type constant. See {@link TokenTypes}. **/
051    private final int tokenType;
052
053    /** The severity level. **/
054    private final SeverityLevel severityLevel;
055
056    /** The id of the module generating the violation. */
057    private final String moduleId;
058
059    /** Key for the violation format. **/
060    private final String key;
061
062    /** Arguments for MessageFormat. */
063    private final Object[] args;
064
065    /** Name of the resource bundle to get violations from. **/
066    private final String bundle;
067
068    /** Class of the source for this Violation. */
069    private final Class<?> sourceClass;
070
071    /** A custom violation overriding the default violation from the bundle. */
072    private final String customMessage;
073
074    /**
075     * Creates a new {@code Violation} instance.
076     *
077     * @param lineNo line number associated with the violation
078     * @param columnNo column number associated with the violation
079     * @param columnCharIndex column char index associated with the violation
080     * @param tokenType token type of the event associated with violation. See {@link TokenTypes}
081     * @param bundle resource bundle name
082     * @param key the key to locate the translation
083     * @param args arguments for the translation
084     * @param severityLevel severity level for the violation
085     * @param moduleId the id of the module the violation is associated with
086     * @param sourceClass the Class that is the source of the violation
087     * @param customMessage optional custom violation overriding the default
088     * @noinspection ConstructorWithTooManyParameters
089     * @noinspectionreason ConstructorWithTooManyParameters - immutable class requires a large
090     *      number of arguments
091     */
092    // -@cs[ParameterNumber] Class is immutable, we need that amount of arguments.
093    public Violation(int lineNo,
094                            int columnNo,
095                            int columnCharIndex,
096                            int tokenType,
097                            String bundle,
098                            String key,
099                            Object[] args,
100                            SeverityLevel severityLevel,
101                            String moduleId,
102                            Class<?> sourceClass,
103                            String customMessage) {
104        this.lineNo = lineNo;
105        this.columnNo = columnNo;
106        this.columnCharIndex = columnCharIndex;
107        this.tokenType = tokenType;
108        this.key = key;
109
110        if (args == null) {
111            this.args = null;
112        }
113        else {
114            this.args = Arrays.copyOf(args, args.length);
115        }
116        this.bundle = bundle;
117        this.severityLevel = severityLevel;
118        this.moduleId = moduleId;
119        this.sourceClass = sourceClass;
120        this.customMessage = customMessage;
121    }
122
123    /**
124     * Creates a new {@code Violation} instance.
125     *
126     * @param lineNo line number associated with the violation
127     * @param columnNo column number associated with the violation
128     * @param tokenType token type of the event associated with violation. See {@link TokenTypes}
129     * @param bundle resource bundle name
130     * @param key the key to locate the translation
131     * @param args arguments for the translation
132     * @param severityLevel severity level for the violation
133     * @param moduleId the id of the module the violation is associated with
134     * @param sourceClass the Class that is the source of the violation
135     * @param customMessage optional custom violation overriding the default
136     * @noinspection ConstructorWithTooManyParameters
137     * @noinspectionreason ConstructorWithTooManyParameters - immutable class requires a large
138     *      number of arguments
139     */
140    // -@cs[ParameterNumber] Class is immutable, we need that amount of arguments.
141    public Violation(int lineNo,
142                            int columnNo,
143                            int tokenType,
144                            String bundle,
145                            String key,
146                            Object[] args,
147                            SeverityLevel severityLevel,
148                            String moduleId,
149                            Class<?> sourceClass,
150                            String customMessage) {
151        this(lineNo, columnNo, columnNo, tokenType, bundle, key, args, severityLevel, moduleId,
152                sourceClass, customMessage);
153    }
154
155    /**
156     * Creates a new {@code Violation} instance.
157     *
158     * @param lineNo line number associated with the violation
159     * @param columnNo column number associated with the violation
160     * @param bundle resource bundle name
161     * @param key the key to locate the translation
162     * @param args arguments for the translation
163     * @param severityLevel severity level for the violation
164     * @param moduleId the id of the module the violation is associated with
165     * @param sourceClass the Class that is the source of the violation
166     * @param customMessage optional custom violation overriding the default
167     * @noinspection ConstructorWithTooManyParameters
168     * @noinspectionreason ConstructorWithTooManyParameters - immutable class requires a large
169     *      number of arguments
170     */
171    // -@cs[ParameterNumber] Class is immutable, we need that amount of arguments.
172    public Violation(int lineNo,
173                            int columnNo,
174                            String bundle,
175                            String key,
176                            Object[] args,
177                            SeverityLevel severityLevel,
178                            String moduleId,
179                            Class<?> sourceClass,
180                            String customMessage) {
181        this(lineNo, columnNo, 0, bundle, key, args, severityLevel, moduleId, sourceClass,
182                customMessage);
183    }
184
185    /**
186     * Creates a new {@code Violation} instance.
187     *
188     * @param lineNo line number associated with the violation
189     * @param columnNo column number associated with the violation
190     * @param bundle resource bundle name
191     * @param key the key to locate the translation
192     * @param args arguments for the translation
193     * @param moduleId the id of the module the violation is associated with
194     * @param sourceClass the Class that is the source of the violation
195     * @param customMessage optional custom violation overriding the default
196     * @noinspection ConstructorWithTooManyParameters
197     * @noinspectionreason ConstructorWithTooManyParameters - immutable class requires a large
198     *      number of arguments
199     */
200    // -@cs[ParameterNumber] Class is immutable, we need that amount of arguments.
201    public Violation(int lineNo,
202                            int columnNo,
203                            String bundle,
204                            String key,
205                            Object[] args,
206                            String moduleId,
207                            Class<?> sourceClass,
208                            String customMessage) {
209        this(lineNo,
210                columnNo,
211             bundle,
212             key,
213             args,
214             DEFAULT_SEVERITY,
215             moduleId,
216             sourceClass,
217             customMessage);
218    }
219
220    /**
221     * Creates a new {@code Violation} instance.
222     *
223     * @param lineNo line number associated with the violation
224     * @param bundle resource bundle name
225     * @param key the key to locate the translation
226     * @param args arguments for the translation
227     * @param severityLevel severity level for the violation
228     * @param moduleId the id of the module the violation is associated with
229     * @param sourceClass the source class for the violation
230     * @param customMessage optional custom violation overriding the default
231     * @noinspection ConstructorWithTooManyParameters
232     * @noinspectionreason ConstructorWithTooManyParameters - immutable class requires a large
233     *      number of arguments
234     */
235    // -@cs[ParameterNumber] Class is immutable, we need that amount of arguments.
236    public Violation(int lineNo,
237                            String bundle,
238                            String key,
239                            Object[] args,
240                            SeverityLevel severityLevel,
241                            String moduleId,
242                            Class<?> sourceClass,
243                            String customMessage) {
244        this(lineNo, 0, bundle, key, args, severityLevel, moduleId,
245                sourceClass, customMessage);
246    }
247
248    /**
249     * Creates a new {@code Violation} instance. The column number
250     * defaults to 0.
251     *
252     * @param lineNo line number associated with the violation
253     * @param bundle name of a resource bundle that contains audit event violations
254     * @param key the key to locate the translation
255     * @param args arguments for the translation
256     * @param moduleId the id of the module the violation is associated with
257     * @param sourceClass the name of the source for the violation
258     * @param customMessage optional custom violation overriding the default
259     */
260    public Violation(
261        int lineNo,
262        String bundle,
263        String key,
264        Object[] args,
265        String moduleId,
266        Class<?> sourceClass,
267        String customMessage) {
268        this(lineNo, 0, bundle, key, args, DEFAULT_SEVERITY, moduleId,
269                sourceClass, customMessage);
270    }
271
272    /**
273     * Gets the line number.
274     *
275     * @return the line number
276     */
277    public int getLineNo() {
278        return lineNo;
279    }
280
281    /**
282     * Gets the column number.
283     *
284     * @return the column number
285     */
286    public int getColumnNo() {
287        return columnNo;
288    }
289
290    /**
291     * Gets the column char index.
292     *
293     * @return the column char index
294     */
295    public int getColumnCharIndex() {
296        return columnCharIndex;
297    }
298
299    /**
300     * Gets the token type.
301     *
302     * @return the token type
303     */
304    public int getTokenType() {
305        return tokenType;
306    }
307
308    /**
309     * Gets the severity level.
310     *
311     * @return the severity level
312     */
313    public SeverityLevel getSeverityLevel() {
314        return severityLevel;
315    }
316
317    /**
318     * Returns id of module.
319     *
320     * @return the module identifier.
321     */
322    public String getModuleId() {
323        return moduleId;
324    }
325
326    /**
327     * Returns the violation key to locate the translation, can also be used
328     * in IDE plugins to map audit event violations to corrective actions.
329     *
330     * @return the violation key
331     */
332    public String getKey() {
333        return key;
334    }
335
336    /**
337     * Gets the name of the source for this Violation.
338     *
339     * @return the name of the source for this Violation
340     */
341    public String getSourceName() {
342        return sourceClass.getName();
343    }
344
345    /**
346     * Indicates whether some other object is "equal to" this one.
347     * Suppression on enumeration is needed so code stays consistent.
348     *
349     * @noinspection EqualsCalledOnEnumConstant
350     * @noinspectionreason EqualsCalledOnEnumConstant - enumeration is needed to keep
351     *      code consistent
352     */
353    // -@cs[CyclomaticComplexity] equals - a lot of fields to check.
354    @Override
355    public boolean equals(Object object) {
356        if (this == object) {
357            return true;
358        }
359        if (object == null || getClass() != object.getClass()) {
360            return false;
361        }
362        final Violation violation = (Violation) object;
363        return Objects.equals(lineNo, violation.lineNo)
364                && Objects.equals(columnNo, violation.columnNo)
365                && Objects.equals(columnCharIndex, violation.columnCharIndex)
366                && Objects.equals(tokenType, violation.tokenType)
367                && Objects.equals(severityLevel, violation.severityLevel)
368                && Objects.equals(moduleId, violation.moduleId)
369                && Objects.equals(key, violation.key)
370                && Objects.equals(bundle, violation.bundle)
371                && Objects.equals(sourceClass, violation.sourceClass)
372                && Objects.equals(customMessage, violation.customMessage)
373                && Arrays.equals(args, violation.args);
374    }
375
376    @Override
377    public int hashCode() {
378        return Objects.hash(lineNo, columnNo, columnCharIndex, tokenType, severityLevel, moduleId,
379                key, bundle, sourceClass, customMessage, Arrays.hashCode(args));
380    }
381
382    ////////////////////////////////////////////////////////////////////////////
383    // Interface Comparable methods
384    ////////////////////////////////////////////////////////////////////////////
385
386    @Override
387    public int compareTo(Violation other) {
388        final int result;
389
390        if (lineNo == other.lineNo) {
391            if (columnNo == other.columnNo) {
392                if (Objects.equals(moduleId, other.moduleId)) {
393                    if (Objects.equals(sourceClass, other.sourceClass)) {
394                        result = getViolation().compareTo(other.getViolation());
395                    }
396                    else if (sourceClass == null) {
397                        result = -1;
398                    }
399                    else if (other.sourceClass == null) {
400                        result = 1;
401                    }
402                    else {
403                        result = sourceClass.getName().compareTo(other.sourceClass.getName());
404                    }
405                }
406                else if (moduleId == null) {
407                    result = -1;
408                }
409                else if (other.moduleId == null) {
410                    result = 1;
411                }
412                else {
413                    result = moduleId.compareTo(other.moduleId);
414                }
415            }
416            else {
417                result = Integer.compare(columnNo, other.columnNo);
418            }
419        }
420        else {
421            result = Integer.compare(lineNo, other.lineNo);
422        }
423        return result;
424    }
425
426    /**
427     * Gets the translated violation.
428     *
429     * @return the translated violation
430     */
431    public String getViolation() {
432        final String violation;
433
434        if (customMessage != null) {
435            violation = new MessageFormat(customMessage, Locale.ROOT).format(args);
436        }
437        else {
438            violation = new LocalizedMessage(bundle, sourceClass, key, args).getMessage();
439        }
440
441        return violation;
442    }
443
444}