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}