1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2026 the original author or authors.
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 ///////////////////////////////////////////////////////////////////////////////////////////////
19
20 package com.puppycrawl.tools.checkstyle.bdd;
21
22 import java.util.Objects;
23
24 /**
25 * Represents a test input violation with line number and message.
26 *
27 * @param lineNo parsed violation line number
28 * @param message parsed violation message
29 */
30 public record TestInputViolation(int lineNo, String message)
31 implements Comparable<TestInputViolation> {
32
33 /** Legacy getter for line number (backward compatibility). */
34 public int getLineNo() {
35 return lineNo;
36 }
37
38 /** Legacy getter for message (backward compatibility). */
39 public String getMessage() {
40 return message;
41 }
42
43 /**
44 * Creates regex string to match the violation message format.
45 *
46 * @return the regex string
47 */
48 public String toRegex() {
49 String regex = lineNo + ":(?:\\d+:)?\\s.*";
50 if (message != null) {
51 regex += escapeSegment(message) + ".*";
52 }
53 return regex;
54 }
55
56 /**
57 * Escapes standard BDD violation special characters in a message segment.
58 *
59 * @param segment the segment to escape
60 * @return the escaped segment
61 */
62 private static String escapeSegment(String segment) {
63 final StringBuilder result = new StringBuilder(segment.length());
64 boolean inQuote = false;
65 int index = 0;
66 while (index < segment.length()) {
67 if (isQuoteStart(segment, index)) {
68 inQuote = true;
69 result.append("\\Q");
70 index += 2;
71 }
72 else if (isQuoteEnd(segment, index)) {
73 inQuote = false;
74 result.append("\\E");
75 index += 2;
76 }
77 else {
78 final char character = segment.charAt(index);
79 if (!inQuote && isSpecialChar(character)) {
80 result.append('\\');
81 }
82 result.append(character);
83 index++;
84 }
85 }
86 return result.toString();
87 }
88
89 /**
90 * Checks if the segment at the given index is the start of a quote block.
91 *
92 * @param segment the segment
93 * @param index the index
94 * @return true if it is the start of a quote block
95 */
96 private static boolean isQuoteStart(String segment, int index) {
97 return index < segment.length() - 1
98 && segment.charAt(index) == '\\'
99 && segment.charAt(index + 1) == 'Q';
100 }
101
102 /**
103 * Checks if the segment at the given index is the end of a quote block.
104 *
105 * @param segment the segment
106 * @param index the index
107 * @return true if it is the end of a quote block
108 */
109 private static boolean isQuoteEnd(String segment, int index) {
110 return index < segment.length() - 1
111 && segment.charAt(index) == '\\'
112 && segment.charAt(index + 1) == 'E';
113 }
114
115 /**
116 * Checks if the character is a special regex character that needs escaping.
117 *
118 * @param character the character
119 * @return true if the character is a special character
120 */
121 private static boolean isSpecialChar(char character) {
122 return character == '{'
123 || character == '('
124 || character == ')'
125 || character == '['
126 || character == ']';
127 }
128
129 @Override
130 public int compareTo(TestInputViolation other) {
131 final int result;
132 if (message != null && lineNo == other.lineNo) {
133 result = message.compareTo(other.message);
134 }
135 else {
136 result = Integer.compare(lineNo, other.lineNo);
137 }
138 return result;
139 }
140
141 @Override
142 public int hashCode() {
143 return Objects.hash(lineNo);
144 }
145
146 @Override
147 public boolean equals(Object object) {
148 return object instanceof TestInputViolation violation
149 && compareTo(violation) == 0;
150 }
151
152 }