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.checks.regexp;
21
22 import java.io.File;
23 import java.util.regex.Pattern;
24
25 import com.puppycrawl.tools.checkstyle.PropertyType;
26 import com.puppycrawl.tools.checkstyle.StatelessCheck;
27 import com.puppycrawl.tools.checkstyle.XdocsPropertyType;
28 import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
29 import com.puppycrawl.tools.checkstyle.api.FileText;
30
31 /**
32 * <div>
33 * Checks that a specified pattern matches across multiple lines in any file type.
34 * </div>
35 *
36 * <p>
37 * Rationale: This check can be used to when the regular expression can be span multiple lines.
38 * </p>
39 *
40 * @since 5.0
41 */
42 @StatelessCheck
43 public class RegexpMultilineCheck extends AbstractFileSetCheck {
44
45 /** A key is pointing to the warning message text in "messages.properties" file. */
46 public static final String MSG_REGEXP_EXCEEDED = "regexp.exceeded";
47
48 /** A key is pointing to the warning message text in "messages.properties" file. */
49 public static final String MSG_REGEXP_MINIMUM = "regexp.minimum";
50
51 /** A key is pointing to the warning message text in "messages.properties" file. */
52 public static final String MSG_EMPTY = "regexp.empty";
53
54 /** A key is pointing to the warning message text in "messages.properties" file. */
55 public static final String MSG_STACKOVERFLOW = "regexp.StackOverflowError";
56
57 /** Specify the format of the regular expression to match. */
58 @XdocsPropertyType(PropertyType.PATTERN)
59 private String format = "$.";
60 /**
61 * Specify the message which is used to notify about violations,
62 * if empty then default (hard-coded) message is used.
63 */
64 private String message;
65 /** Specify the minimum number of matches required in each file. */
66 private int minimum;
67 /** Specify the maximum number of matches required in each file. */
68 private int maximum;
69 /** Control whether to ignore case when searching. */
70 private boolean ignoreCase;
71 /** Control whether to match expressions across multiple lines. */
72 private boolean matchAcrossLines;
73
74 /** The detector to use. */
75 private MultilineDetector detector;
76
77 /**
78 * Creates a new {@code RegexpMultilineCheck} instance.
79 */
80 public RegexpMultilineCheck() {
81 // no code by default
82 }
83
84 @Override
85 public void beginProcessing(String charset) {
86 final DetectorOptions options = DetectorOptions.newBuilder()
87 .reporter(this)
88 .compileFlags(getRegexCompileFlags())
89 .format(format)
90 .message(message)
91 .minimum(minimum)
92 .maximum(maximum)
93 .ignoreCase(ignoreCase)
94 .build();
95 detector = new MultilineDetector(options,
96 MSG_REGEXP_EXCEEDED, MSG_REGEXP_MINIMUM,
97 MSG_EMPTY, MSG_STACKOVERFLOW);
98 }
99
100 @Override
101 protected void processFiltered(File file, FileText fileText) {
102 detector.processLines(fileText);
103 }
104
105 /**
106 * Retrieves the compile-flags for the regular expression being built based
107 * on {@code matchAcrossLines}.
108 *
109 * @return The compile-flags.
110 */
111 private int getRegexCompileFlags() {
112 final int result;
113
114 if (matchAcrossLines) {
115 result = Pattern.DOTALL;
116 }
117 else {
118 result = Pattern.MULTILINE;
119 }
120
121 return result;
122 }
123
124 /**
125 * Setter to specify the format of the regular expression to match.
126 *
127 * @param format the format of the regular expression to match.
128 * @since 5.0
129 */
130 public void setFormat(String format) {
131 this.format = format;
132 }
133
134 /**
135 * Setter to specify the message which is used to notify about violations,
136 * if empty then default (hard-coded) message is used.
137 *
138 * @param message the message to report for a match.
139 * @since 5.0
140 */
141 public void setMessage(String message) {
142 this.message = message;
143 }
144
145 /**
146 * Setter to specify the minimum number of matches required in each file.
147 *
148 * @param minimum the minimum number of matches required in each file.
149 * @since 5.0
150 */
151 public void setMinimum(int minimum) {
152 this.minimum = minimum;
153 }
154
155 /**
156 * Setter to specify the maximum number of matches required in each file.
157 *
158 * @param maximum the maximum number of matches required in each file.
159 * @since 5.0
160 */
161 public void setMaximum(int maximum) {
162 this.maximum = maximum;
163 }
164
165 /**
166 * Setter to control whether to ignore case when searching.
167 *
168 * @param ignoreCase whether to ignore case when searching.
169 * @since 5.0
170 */
171 public void setIgnoreCase(boolean ignoreCase) {
172 this.ignoreCase = ignoreCase;
173 }
174
175 /**
176 * Setter to control whether to match expressions across multiple lines.
177 *
178 * @param matchAcrossLines whether to match expressions across multiple lines.
179 * @since 8.25
180 */
181 public void setMatchAcrossLines(boolean matchAcrossLines) {
182 this.matchAcrossLines = matchAcrossLines;
183 }
184
185 }