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