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 /** 392 * {@inheritDoc} 393 * 394 * <p>The same fields as in {@link #equals(Object)} take part in the 395 * comparison, so that a result of zero is consistent with equality, as the 396 * {@link Comparable} contract recommends. Line, column, module id, source 397 * class, and the rendered violation stay the leading criteria; the 398 * remaining fields only break a tie between violations that would 399 * otherwise be reported at the same place with the same text. 400 */ 401 @Override 402 public int compareTo(Violation other) { 403 int result = compareLocation(other); 404 if (result == 0) { 405 result = compareContent(other); 406 } 407 return result; 408 } 409 410 /** 411 * Compares the place and the text a violation is reported with. 412 * 413 * @param other the violation to compare against 414 * @return the value {@code 0} if the violations are reported the same way 415 */ 416 private int compareLocation(Violation other) { 417 int result = Integer.compare(lineNo, other.lineNo); 418 if (result == 0) { 419 result = Integer.compare(columnNo, other.columnNo); 420 } 421 if (result == 0) { 422 result = compareNullable(moduleId, other.moduleId); 423 } 424 if (result == 0) { 425 result = compareNullable(getSourceClassName(), other.getSourceClassName()); 426 } 427 if (result == 0) { 428 result = getViolation().compareTo(other.getViolation()); 429 } 430 return result; 431 } 432 433 /** 434 * Compares the fields that two violations reported at the same place with 435 * the same text can still differ in. 436 * 437 * @param other the violation to compare against 438 * @return the value {@code 0} if the violations carry the same content 439 */ 440 private int compareContent(Violation other) { 441 int result = Integer.compare(columnCharIndex, other.columnCharIndex); 442 if (result == 0) { 443 result = Integer.compare(tokenType, other.tokenType); 444 } 445 if (result == 0) { 446 result = severityLevel.compareTo(other.severityLevel); 447 } 448 if (result == 0) { 449 result = compareNullable(key, other.key); 450 } 451 if (result == 0) { 452 result = compareNullable(bundle, other.bundle); 453 } 454 if (result == 0) { 455 result = compareNullable(customMessage, other.customMessage); 456 } 457 if (result == 0) { 458 result = Arrays.deepToString(args).compareTo(Arrays.deepToString(other.args)); 459 } 460 return result; 461 } 462 463 /** 464 * Compares two values that may be {@code null}, ordering {@code null} 465 * before any other value. 466 * 467 * @param first the first value 468 * @param second the second value 469 * @return a negative integer, zero, or a positive integer as the first 470 * value is less than, equal to, or greater than the second 471 */ 472 private static int compareNullable(@Nullable String first, @Nullable String second) { 473 final int result; 474 if (first == null) { 475 if (second == null) { 476 result = 0; 477 } 478 else { 479 result = -1; 480 } 481 } 482 else if (second == null) { 483 result = 1; 484 } 485 else { 486 result = first.compareTo(second); 487 } 488 return result; 489 } 490 491 /** 492 * Gets the source class name or {@code null} when no source class is set. 493 * 494 * @return the source class name or {@code null} 495 */ 496 @Nullable 497 private String getSourceClassName() { 498 final String name; 499 if (sourceClass == null) { 500 name = null; 501 } 502 else { 503 name = sourceClass.getName(); 504 } 505 return name; 506 } 507 508 /** 509 * Gets the translated violation. 510 * 511 * @return the translated violation 512 */ 513 public String getViolation() { 514 final String violation; 515 516 if (customMessage != null) { 517 violation = new MessageFormat(customMessage, Locale.ROOT).format(args); 518 } 519 else { 520 violation = new LocalizedMessage(bundle, sourceClass, key, args).getMessage(); 521 } 522 523 return violation; 524 } 525 526}