View Javadoc
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.io.File;
23  import java.io.IOException;
24  import java.io.StringReader;
25  import java.math.BigDecimal;
26  import java.nio.file.Files;
27  import java.nio.file.Path;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.BitSet;
31  import java.util.Collection;
32  import java.util.Collections;
33  import java.util.HashMap;
34  import java.util.HashSet;
35  import java.util.List;
36  import java.util.Locale;
37  import java.util.Map;
38  import java.util.Properties;
39  import java.util.Set;
40  import java.util.regex.Matcher;
41  import java.util.regex.Pattern;
42  import java.util.stream.Collectors;
43  
44  import org.xml.sax.InputSource;
45  
46  import com.puppycrawl.tools.checkstyle.ConfigurationLoader;
47  import com.puppycrawl.tools.checkstyle.PropertiesExpander;
48  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
49  import com.puppycrawl.tools.checkstyle.api.Configuration;
50  import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
51  import com.puppycrawl.tools.checkstyle.meta.ModuleDetails;
52  import com.puppycrawl.tools.checkstyle.meta.ModulePropertyDetails;
53  import com.puppycrawl.tools.checkstyle.meta.XmlMetaReader;
54  import com.puppycrawl.tools.checkstyle.utils.InlineConfigUtils;
55  import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
56  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
57  
58  public final class InlineConfigParser {
59  
60      /** A pattern matching the symbol: "\" or "/". */
61      private static final Pattern SLASH_PATTERN = Pattern.compile("[\\\\/]");
62  
63      /**
64       * Triple-quote delimiter used to start and end a multiline violation message.
65       *
66       * <p>The parser assembles all continuation {@code //} lines until a line
67       * ending with {@code """} is found, joining them with a single space.
68       * See :
69       * <a href="https://github.com/checkstyle/checkstyle/blob/master/docs/specifying-violations.md"
70       * >specifying-violations</a>.
71       */
72      private static final String TRIPLE_QUOTE = "\"\"\"";
73  
74      /**
75       * Pattern to match a continuation line of a multiline triple-quote violation message.
76       * Captures everything after the leading {@code //} and optional whitespace.
77       */
78      private static final Pattern MULTILINE_CONTINUATION_PATTERN =
79              Pattern.compile("^\\s*//(.*)$");
80  
81      /**
82       * Pattern for lines under
83       * {@link InlineConfigParser#VIOLATIONS_SOME_LINES_ABOVE_PATTERN}.
84       */
85      private static final Pattern VIOLATION_MESSAGE_PATTERN = Pattern
86              .compile(".*//\\s*(?:['\"](.*)['\"])?$");
87      /**
88       * A pattern that matches the following comments formats.
89       * <ol>
90       *     <li> // violation </li>
91       *     <li> // violation, 'violation message' </li>
92       *     <li> // violation 'violation messages' </li>
93       *     <li> // violation, "violation messages" </li>
94       *     <li> // violation """multiline violation message </li>
95       * </ol>
96       *
97       * <p>
98       * This pattern will not match the following formats.
99       * <ol>
100      *     <li> // violation, explanation </li>
101      *     <li> // violation, explanation, 'violation message' </li>
102      * </ol>
103      *
104      * These are matched by
105      * {@link InlineConfigParser#VIOLATION_WITH_EXPLANATION_PATTERN}.
106      * </p>
107      */
108     private static final Pattern VIOLATION_PATTERN = Pattern
109             .compile(".*//\\s*violation\\s*(?:['\"](.*)['\"])?$");
110 
111     /** A pattern to find the string: "// violation above". */
112     private static final Pattern VIOLATION_ABOVE_PATTERN = Pattern
113             .compile(".*//\\s*violation above\\s*(?:['\"](.*))?$");
114 
115     /** A pattern to find the string: "// violation below". */
116     private static final Pattern VIOLATION_BELOW_PATTERN = Pattern
117             .compile(".*//\\s*violation below\\s*(?:['\"](.*))?$");
118 
119     /** A pattern to find the string: "// violation above, explanation". */
120     private static final Pattern VIOLATION_ABOVE_WITH_EXPLANATION_PATTERN = Pattern
121             .compile(".*//\\s*violation above,\\s.+\\s(?:['\"](.*)['\"])?$");
122 
123     /** A pattern to find the string: "// violation below, explanation". */
124     private static final Pattern VIOLATION_BELOW_WITH_EXPLANATION_PATTERN = Pattern
125             .compile(".*//\\s*violation below,\\s.+\\s(?:['\"](.*)['\"])?$");
126 
127     /** A pattern to find the string: "// violation, explanation". */
128     private static final Pattern VIOLATION_WITH_EXPLANATION_PATTERN = Pattern
129             .compile(".*//\\s*violation,\\s+(?:.*)?$");
130 
131     /** A pattern to find the string: "// X violations". */
132     private static final Pattern MULTIPLE_VIOLATIONS_PATTERN = Pattern
133             .compile(".*//\\s*(\\d+) violations$");
134 
135     /** A pattern to find the string: "// X violations above". */
136     private static final Pattern MULTIPLE_VIOLATIONS_ABOVE_PATTERN = Pattern
137             .compile(".*//\\s*(\\d+) violations above$");
138 
139     /** A pattern to find the string: "// X violations below". */
140     private static final Pattern MULTIPLE_VIOLATIONS_BELOW_PATTERN = Pattern
141             .compile(".*//\\s*(\\d+) violations below$");
142 
143     /** A pattern to find the string: "// filtered violation". */
144     private static final Pattern FILTERED_VIOLATION_PATTERN = Pattern
145             .compile(".*//\\s*filtered violation\\s*(?:['\"](.*)['\"])?$");
146 
147     /** A pattern to find the string: "// filtered violation above". */
148     private static final Pattern FILTERED_VIOLATION_ABOVE_PATTERN = Pattern
149             .compile(".*//\\s*filtered violation above\\s*(?:['\"](.*)['\"])?$");
150 
151     /** A pattern to find the string: "// filtered violation below". */
152     private static final Pattern FILTERED_VIOLATION_BELOW_PATTERN = Pattern
153             .compile(".*//\\s*filtered violation below\\s*(?:['\"](.*)['\"])?$");
154 
155     /** A pattern to find the string: "// filtered violation X lines above". */
156     private static final Pattern FILTERED_VIOLATION_SOME_LINES_ABOVE_PATTERN = Pattern
157             .compile(".*//\\s*filtered violation (\\d+) lines above\\s*(?:['\"](.*))?$");
158 
159     /** A pattern to find the string: "// filtered violation X lines below". */
160     private static final Pattern FILTERED_VIOLATION_SOME_LINES_BELOW_PATTERN = Pattern
161             .compile(".*//\\s*filtered violation (\\d+) lines below\\s*(?:['\"](.*))?$");
162 
163     /** A pattern to find the string: "// violation X lines above". */
164     private static final Pattern VIOLATION_SOME_LINES_ABOVE_PATTERN = Pattern
165             .compile(".*//\\s*violation (\\d+) lines above\\s*(?:['\"](.*))?$");
166 
167     /** A pattern to find the string: "// violation X lines below". */
168     private static final Pattern VIOLATION_SOME_LINES_BELOW_PATTERN = Pattern
169             .compile(".*//\\s*violation (\\d+) lines below\\s*(?:['\"](.*))?$");
170 
171     /** A pattern to find the string: "// violation first line". */
172     private static final Pattern VIOLATION_FIRST_LINE_PATTERN = Pattern
173             .compile(".*//\\s*violation first line\\s*(?:['\"](.*))?$");
174 
175     /** A pattern to find the string: "// violation last line". */
176     private static final Pattern VIOLATION_LAST_LINE_PATTERN = Pattern
177             .compile(".*//\\s*violation last line\\s*(?:['\"](.*))?$");
178 
179     /**
180      * <div>
181      * Multiple violations for above line. Messages are X lines below.
182      * {@code
183      *   // X violations above:
184      *   //                    'violation message1'
185      *   //                    'violation messageX'
186      * }
187      *
188      * Messages are matched by {@link InlineConfigParser#VIOLATION_MESSAGE_PATTERN}
189      * </div>
190      */
191     private static final Pattern VIOLATIONS_ABOVE_PATTERN_WITH_MESSAGES = Pattern
192             .compile(".*//\\s*(\\d+) violations above:$");
193 
194     /**
195      * <div>
196      * Multiple violations for line. Violations are Y lines above, messages are X lines below.
197      * {@code
198      *   // X violations Y lines above:
199      *   //                            'violation message1'
200      *   //                            'violation messageX'
201      * }
202      *
203      * Messages are matched by {@link InlineConfigParser#VIOLATION_MESSAGE_PATTERN}
204      * </div>
205      */
206     private static final Pattern VIOLATIONS_SOME_LINES_ABOVE_PATTERN = Pattern
207             .compile(".*//\\s*(\\d+) violations (\\d+) lines above:$");
208 
209     /**
210      * <div>
211      * Multiple violations for line. Violations are Y lines below, messages are X lines below.
212      * {@code
213      *   // X violations Y lines below:
214      *   //                            'violation message1'
215      *   //                            'violation messageX'
216      * }
217      *
218      * Messages are matched by {@link InlineConfigParser#VIOLATION_MESSAGE_PATTERN}
219      * </div>
220      */
221     private static final Pattern VIOLATIONS_SOME_LINES_BELOW_PATTERN = Pattern
222             .compile(".*//\\s*(\\d+) violations (\\d+) lines below:$");
223 
224     /** A pattern that matches any comment by default. */
225     private static final Pattern VIOLATION_DEFAULT = Pattern
226             .compile(".*//.*violation.*");
227 
228     /** The String "(null)". */
229     private static final String NULL_STRING = "(null)";
230 
231     private static final String LATEST_DTD = String.format(Locale.ROOT,
232             "<!DOCTYPE module PUBLIC \"%s\" \"%s\">%n",
233             ConfigurationLoader.DTD_PUBLIC_CS_ID_1_3,
234             ConfigurationLoader.DTD_PUBLIC_CS_ID_1_3);
235 
236     /**
237      * ALLOWED: any code, then "// ok" or "// violation" (lowercase),
238      * optionally followed by either a space or a comma (with optional spaces)
239      * plus explanation text.
240      */
241     private static final Pattern ALLOWED_OK_VIOLATION_PATTERN =
242             Pattern.compile(".*//\\s*(ok|violation)\\b(?:[ ,]\\s*.*)?$");
243 
244     /**
245      * DETECT any comment containing ok/violation in any case/spacing.
246      */
247     private static final Pattern ANY_OK_VIOLATION_PATTERN =
248             Pattern.compile(".*//\\s*(?i)(ok|violation).*");
249 
250     /**
251      *  Inlined configs can not be used in non-java checks, as Inlined config is java style
252      *  multiline comment.
253      *  Such check files needs to be permanently suppressed.
254      */
255     private static final Set<String> PERMANENT_SUPPRESSED_CHECKS = Set.of(
256             // Inlined config is not supported for non java files.
257             "com.puppycrawl.tools.checkstyle.checks.OrderedPropertiesCheck",
258             "com.puppycrawl.tools.checkstyle.checks.UniquePropertiesCheck",
259             "com.puppycrawl.tools.checkstyle.checks.TranslationCheck"
260     );
261 
262     /**
263      * Checks in which violation message is not yet fully specified in all input files.
264      * Temporary suppression until
265      * <a href="https://github.com/checkstyle/checkstyle/issues/15456">#15456</a>
266      * is fully resolved for these checks. Remove entries here as their input files
267      * are updated with proper violation messages.
268      */
269     private static final Set<String> SUPPRESSED_CHECKS = Set.of(
270             "com.puppycrawl.tools.checkstyle.checks.blocks.EmptyBlockCheck",
271             "com.puppycrawl.tools.checkstyle.checks.blocks.LeftCurlyCheck",
272             "com.puppycrawl.tools.checkstyle.checks.coding.EqualsAvoidNullCheck",
273             "com.puppycrawl.tools.checkstyle.checks.coding.ExplicitInitializationCheck",
274             "com.puppycrawl.tools.checkstyle.checks.coding.FallThroughCheck",
275             "com.puppycrawl.tools.checkstyle.checks.coding.FinalLocalVariableCheck",
276             "com.puppycrawl.tools.checkstyle.checks.coding.HiddenFieldCheck",
277             "com.puppycrawl.tools.checkstyle.checks.coding.ModifiedControlVariableCheck",
278             "com.puppycrawl.tools.checkstyle.checks.coding.MultipleStringLiteralsCheck",
279             "com.puppycrawl.tools.checkstyle.checks.coding.MultipleVariableDeclarationsCheck",
280             "com.puppycrawl.tools.checkstyle.checks.coding.RequireThisCheck",
281             "com.puppycrawl.tools.checkstyle.checks.coding.SimplifyBooleanExpressionCheck",
282             "com.puppycrawl.tools.checkstyle.checks.coding.UnnecessaryParenthesesCheck",
283             "com.puppycrawl.tools.checkstyle.checks.coding.VariableDeclarationUsageDistanceCheck",
284             "com.puppycrawl.tools.checkstyle.checks.design.HideUtilityClassConstructorCheck",
285             "com.puppycrawl.tools.checkstyle.checks.design.VisibilityModifierCheck",
286             "com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck",
287             "com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocMethodCheck",
288             "com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTypeCheck",
289             "com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocVariableCheck",
290             "com.puppycrawl.tools.checkstyle.checks.naming.ParameterNameCheck",
291             "com.puppycrawl.tools.checkstyle.checks.sizes.MethodCountCheck",
292             "com.puppycrawl.tools.checkstyle.checks.whitespace.EmptyLineSeparatorCheck",
293             "com.puppycrawl.tools.checkstyle.checks.whitespace.GenericWhitespaceCheck",
294             "com.puppycrawl.tools.checkstyle.checks.whitespace.OperatorWrapCheck",
295             "com.puppycrawl.tools.checkstyle.checks.whitespace.WhitespaceAfterCheck"
296     );
297 
298     /**
299      * Input files where violation messages are not yet fully specified with quoted
300      * messages. Temporary suppression until these files are updated with proper
301      * violation messages. Remove entries here as their input files are fixed.
302      * <a href="https://github.com/checkstyle/checkstyle/issues/20954">#20954</a>
303      */
304     private static final Set<String> SUPPRESSED_VALIDATE_MESSAGE_FILES = Set.of(
305             "checks/coding/equalshashcode/Example1.java",
306             "checks/coding/noclone/Example1.java",
307             "checks/coding/unusedlocalvariable/Example1.java",
308             "checks/sizes/recordcomponentnumber/Example2.java",
309             "checks/whitespace/separatorwrap/Example1.java",
310             "com/google/checkstyle/test/chapter5naming/rule53camelcase/"
311                     + "InputUnderscoreUsedInNames.java"
312     );
313 
314     /**
315      * Input files where default values for properties are intentionally not specified.
316      * This two required for pitest coverage in javadoc and util profiles:
317      * checks/javadoc/abstractjavadoc/InputAbstractJavadocTokensPass.java
318      */
319     private static final Set<String> SUPPRESSED_VALIDATE_DEFAULT_FILES = Set.of(
320         "checks/coding/matchxpath/InputMatchXpath2.java",
321         "checks/javadoc/abstractjavadoc/InputAbstractJavadocTokensFail.java",
322         "checks/javadoc/abstractjavadoc/InputAbstractJavadocNonTightHtmlTags2.java",
323         "checks/javadoc/abstractjavadoc/InputAbstractJavadocNonTightHtmlTags3.java",
324         "checks/javadoc/abstractjavadoc/InputAbstractJavadocNonTightHtmlTagsOne.java",
325         "checks/javadoc/abstractjavadoc/InputAbstractJavadocNonTightHtmlTagsTwo.java",
326         "checks/javadoc/abstractjavadoc/InputAbstractJavadocNonTightHtmlTagsVisitCountOne.java",
327         "checks/javadoc/abstractjavadoc/InputAbstractJavadocNonTightHtmlTagsVisitCountTwo.java",
328         "checks/javadoc/abstractjavadoc/InputAbstractJavadocTokensPass.java",
329         "checkstyle/checks/imports/importcontrol/InputImportControlFileNameNoExtension"
330     );
331 
332     // This is a hack until https://github.com/checkstyle/checkstyle/issues/13845
333     private static final Map<String, String> MODULE_MAPPINGS = new HashMap<>();
334 
335     private static final Map<String, ModuleDetails> PUBLIC_MODULE_DETAILS_MAP = new HashMap<>();
336 
337     // -@cs[ExecutableStatementCount] Suppressing due to large module mappings
338     static {
339         MODULE_MAPPINGS.put("IllegalCatch",
340                 "com.puppycrawl.tools.checkstyle.checks.coding.IllegalCatchCheck");
341         MODULE_MAPPINGS.put("MagicNumber",
342                 "com.puppycrawl.tools.checkstyle.checks.coding.MagicNumberCheck");
343         MODULE_MAPPINGS.put("SummaryJavadoc",
344                 "com.puppycrawl.tools.checkstyle.checks.javadoc.SummaryJavadocCheck");
345         MODULE_MAPPINGS.put("ClassDataAbstractionCoupling",
346                 "com.puppycrawl.tools.checkstyle.checks.metrics.ClassDataAbstractionCouplingCheck");
347         MODULE_MAPPINGS.put("ConstantName",
348                 "com.puppycrawl.tools.checkstyle.checks.naming.ConstantNameCheck");
349         MODULE_MAPPINGS.put("MemberName",
350                 "com.puppycrawl.tools.checkstyle.checks.naming.MemberNameCheck");
351         MODULE_MAPPINGS.put("GoogleNonConstantFieldName",
352                 "com.puppycrawl.tools.checkstyle.checks.naming.GoogleNonConstantFieldNameCheck");
353         MODULE_MAPPINGS.put("MethodName",
354                 "com.puppycrawl.tools.checkstyle.checks.naming.MethodNameCheck");
355         MODULE_MAPPINGS.put("GoogleMethodName",
356                 "com.puppycrawl.tools.checkstyle.checks.naming.GoogleMethodNameCheck");
357         MODULE_MAPPINGS.put("ParameterName",
358                 "com.puppycrawl.tools.checkstyle.checks.naming.ParameterNameCheck");
359         MODULE_MAPPINGS.put("RegexpOnFilename",
360                 "com.puppycrawl.tools.checkstyle.checks.regexp.RegexpOnFilenameCheck");
361         MODULE_MAPPINGS.put("RegexpSingleline",
362                 "com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineCheck");
363         MODULE_MAPPINGS.put("RegexpSinglelineJava",
364                 "com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck");
365         MODULE_MAPPINGS.put("LineLength",
366                 "com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck");
367         MODULE_MAPPINGS.put("ParameterNumber",
368                 "com.puppycrawl.tools.checkstyle.checks.sizes.ParameterNumberCheck");
369         MODULE_MAPPINGS.put("NoWhitespaceAfter",
370                 "com.puppycrawl.tools.checkstyle.checks.whitespace.NoWhitespaceAfterCheck");
371         MODULE_MAPPINGS.put("OrderedProperties",
372                 "com.puppycrawl.tools.checkstyle.checks.OrderedPropertiesCheck");
373         MODULE_MAPPINGS.put("SuppressWarningsHolder",
374                 "com.puppycrawl.tools.checkstyle.checks.SuppressWarningsHolder");
375         MODULE_MAPPINGS.put("UniqueProperties",
376                 "com.puppycrawl.tools.checkstyle.checks.UniquePropertiesCheck");
377         MODULE_MAPPINGS.put("SuppressionXpathSingleFilter",
378                 "com.puppycrawl.tools.checkstyle.filters.SuppressionXpathSingleFilter");
379         MODULE_MAPPINGS.put("SuppressWarningsFilter",
380                 "com.puppycrawl.tools.checkstyle.filters.SuppressWarningsFilter");
381         MODULE_MAPPINGS.put("LeftCurly",
382                 "com.puppycrawl.tools.checkstyle.checks.blocks.LeftCurlyCheck");
383         MODULE_MAPPINGS.put("RequireThis",
384                 "com.puppycrawl.tools.checkstyle.checks.coding.RequireThisCheck");
385         MODULE_MAPPINGS.put("IllegalThrows",
386                 "com.puppycrawl.tools.checkstyle.checks.coding.IllegalThrowsCheck");
387         MODULE_MAPPINGS.put("LocalFinalVariableName",
388                 "com.puppycrawl.tools.checkstyle.checks.naming.LocalFinalVariableNameCheck");
389         MODULE_MAPPINGS.put("PackageName",
390                 "com.puppycrawl.tools.checkstyle.checks.naming.PackageNameCheck");
391         MODULE_MAPPINGS.put("RedundantModifier",
392                 "com.puppycrawl.tools.checkstyle.checks.modifier.RedundantModifierCheck");
393         MODULE_MAPPINGS.put("AbstractClassName",
394                 "com.puppycrawl.tools.checkstyle.checks.naming.AbstractClassNameCheck");
395         MODULE_MAPPINGS.put("JavadocMethod",
396                 "com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocMethodCheck");
397         MODULE_MAPPINGS.put("IllegalIdentifierName",
398                 "com.puppycrawl.tools.checkstyle.checks.naming.IllegalIdentifierNameCheck");
399         MODULE_MAPPINGS.put("FileLength",
400                 "com.puppycrawl.tools.checkstyle.checks.sizes.FileLengthCheck");
401         MODULE_MAPPINGS.put("EqualsAvoidNull",
402                 "com.puppycrawl.tools.checkstyle.checks.coding.EqualsAvoidNullCheck");
403         MODULE_MAPPINGS.put("CyclomaticComplexity",
404                 "com.puppycrawl.tools.checkstyle.checks.metrics.CyclomaticComplexityCheck");
405         MODULE_MAPPINGS.put("EmptyLineSeparator",
406                 "com.puppycrawl.tools.checkstyle.checks.whitespace.EmptyLineSeparatorCheck");
407         MODULE_MAPPINGS.put("LocalVariableName",
408                 "com.puppycrawl.tools.checkstyle.checks.naming.LocalVariableNameCheck");
409         MODULE_MAPPINGS.put("ModifierOrder",
410                 "com.puppycrawl.tools.checkstyle.checks.modifier.ModifierOrderCheck");
411     }
412 
413     /** Stop instances being created. */
414     private InlineConfigParser() {
415     }
416 
417     public static TestInputConfiguration parse(String inputFilePath) throws Exception {
418         return parse(inputFilePath, false);
419     }
420 
421     /**
422      * Parses the input file provided.
423      *
424      * @param inputFilePath the input file path.
425      * @param setFilteredViolations flag to set filtered violations.
426      * @throws Exception if unable to read file or file not formatted properly.
427      */
428     private static TestInputConfiguration parse(String inputFilePath,
429                                                 boolean setFilteredViolations) throws Exception {
430         final TestInputConfiguration.Builder testInputConfigBuilder =
431                 new TestInputConfiguration.Builder();
432         final Path filePath = Path.of(inputFilePath);
433         final List<String> lines = readFile(filePath);
434         try {
435             setModules(testInputConfigBuilder, inputFilePath, lines);
436         }
437         catch (Exception exc) {
438             throw new CheckstyleException("Config comment not specified properly in "
439                     + inputFilePath, exc);
440         }
441         try {
442             setViolations(testInputConfigBuilder, lines, setFilteredViolations, inputFilePath);
443         }
444         catch (CheckstyleException exc) {
445             throw new CheckstyleException("Failed to set violations in " + inputFilePath, exc);
446         }
447         return testInputConfigBuilder.build();
448     }
449 
450     public static List<TestInputViolation> getViolationsFromInputFile(String inputFilePath)
451             throws Exception {
452         final TestInputConfiguration.Builder testInputConfigBuilder =
453                 new TestInputConfiguration.Builder();
454         final Path filePath = Path.of(inputFilePath);
455         final List<String> lines = readFile(filePath);
456 
457         try {
458             for (int lineNo = 0; lineNo < lines.size(); lineNo++) {
459                 setViolations(testInputConfigBuilder, lines, false, lineNo, true);
460             }
461         }
462         catch (CheckstyleException exc) {
463             throw new CheckstyleException("Failed to set violations in " + inputFilePath, exc);
464         }
465 
466         return testInputConfigBuilder.build().violations();
467     }
468 
469     public static List<TestInputViolation> getFilteredViolationsFromInputFile(String inputFilePath)
470             throws Exception {
471         final TestInputConfiguration.Builder testInputConfigBuilder =
472                 new TestInputConfiguration.Builder();
473         final Path filePath = Path.of(inputFilePath);
474         final List<String> lines = readFile(filePath);
475 
476         try {
477             for (int lineNo = 0; lineNo < lines.size(); lineNo++) {
478                 setViolations(testInputConfigBuilder, lines, true, lineNo, true);
479             }
480         }
481         catch (CheckstyleException exc) {
482             throw new CheckstyleException("Failed to set violations in " + inputFilePath, exc);
483         }
484 
485         return testInputConfigBuilder.build().filteredViolations();
486     }
487 
488     public static TestInputConfiguration parseWithFilteredViolations(String inputFilePath)
489             throws Exception {
490         return parse(inputFilePath, true);
491     }
492 
493     /**
494      * Parse the input file with configuration in xml header.
495      *
496      * @param inputFilePath the input file path.
497      * @throws Exception if unable to parse the xml header
498      */
499     public static TestInputConfiguration parseWithXmlHeader(String inputFilePath)
500             throws Exception {
501 
502         final Path filePath = Path.of(inputFilePath);
503         final List<String> lines = readFile(filePath);
504         final InlineConfigUtils.MatchedDelimiter matched =
505                 InlineConfigUtils.matchDelimiter(lines, inputFilePath);
506         if (matched == null || !matched.xmlStyleConfig()) {
507             throw new CheckstyleException("Config cannot be parsed as xml.");
508         }
509 
510         final List<String> inlineConfig = getInlineConfig(lines, inputFilePath, matched);
511         final String stringXmlConfig = LATEST_DTD + String.join("", inlineConfig);
512         final InputSource inputSource = new InputSource(new StringReader(stringXmlConfig));
513         final Configuration xmlConfig = ConfigurationLoader.loadConfiguration(
514                 inputSource, new PropertiesExpander(System.getProperties()),
515                 ConfigurationLoader.IgnoredModulesOptions.EXECUTE
516         );
517         final String configName = xmlConfig.getName();
518         if (!"Checker".equals(configName)) {
519             throw new CheckstyleException(
520                     "First module should be Checker, but was " + configName);
521         }
522 
523         final TestInputConfiguration.Builder testInputConfigBuilder =
524                 new TestInputConfiguration.Builder();
525         testInputConfigBuilder.setXmlConfiguration(xmlConfig);
526         try {
527             setViolations(testInputConfigBuilder, lines, false, inputFilePath);
528         }
529         catch (CheckstyleException exc) {
530             throw new CheckstyleException("Failed to set violations in " + inputFilePath, exc);
531         }
532         return testInputConfigBuilder.buildWithXmlConfiguration();
533     }
534 
535     private static void setModules(TestInputConfiguration.Builder testInputConfigBuilder,
536                                    String inputFilePath, List<String> lines)
537             throws Exception {
538         final InlineConfigUtils.MatchedDelimiter matched =
539                 InlineConfigUtils.matchDelimiter(lines, inputFilePath);
540         if (matched == null) {
541             throw new CheckstyleException("Config not specified on top. Expected "
542                     + InlineConfigUtils.describeExpectedDelimiters(inputFilePath)
543                     + " as the first line. Please see other inputs for examples of what"
544                     + " is required.");
545         }
546 
547         final List<String> inlineConfig = getInlineConfig(lines, inputFilePath, matched);
548 
549         if (matched.xmlStyleConfig()) {
550             final String stringXmlConfig = LATEST_DTD + String.join("", inlineConfig);
551             final InputSource inputSource = new InputSource(new StringReader(stringXmlConfig));
552             final Configuration xmlConfig = ConfigurationLoader.loadConfiguration(
553                     inputSource, new PropertiesExpander(System.getProperties()),
554                     ConfigurationLoader.IgnoredModulesOptions.EXECUTE
555             );
556             final String configName = xmlConfig.getName();
557             if (!"Checker".equals(configName)) {
558                 throw new CheckstyleException(
559                         "First module should be Checker, but was " + configName);
560             }
561             handleXmlConfig(testInputConfigBuilder, inputFilePath, xmlConfig.getChildren());
562         }
563         else {
564             handleKeyValueConfig(testInputConfigBuilder, inputFilePath, inlineConfig);
565         }
566     }
567 
568     /**
569      * Extracts the raw config lines (between the start and end delimiter) for the given
570      * file, stripping the leading {@code #} comment marker from each line when the target
571      * file is a {@code .properties} file (since every config line there must itself be a
572      * valid properties-file comment).
573      *
574      * @param lines all lines of the file.
575      * @param inputFilePath the input file path, used to select the delimiter and
576      *     line-prefix-stripping behavior.
577      * @return the inline config lines, ready to be parsed as XML or key-value pairs.
578      */
579     private static List<String> getInlineConfig(List<String> lines, String inputFilePath,
580                                                 InlineConfigUtils.MatchedDelimiter matched) {
581         final int endIndex = InlineConfigUtils.getConfigEndIndex(lines, matched);
582         final int startIndex;
583         if (matched.end() == null) {
584             startIndex = 0;
585         }
586         else {
587             startIndex = 1;
588         }
589         final List<String> rawConfigLines = lines.subList(startIndex, endIndex);
590 
591         final List<String> result;
592         if (inputFilePath.endsWith(".properties")) {
593             result = InlineConfigUtils.stripPropertiesCommentPrefix(rawConfigLines);
594         }
595         else {
596             result = rawConfigLines;
597         }
598         return result;
599     }
600 
601     private static void handleXmlConfig(TestInputConfiguration.Builder testInputConfigBuilder,
602                                         String inputFilePath,
603                                         Configuration... modules)
604             throws CheckstyleException {
605 
606         for (Configuration module: modules) {
607             final String moduleName = module.getName();
608             if ("TreeWalker".equals(moduleName)) {
609                 handleXmlConfig(testInputConfigBuilder, inputFilePath, module.getChildren());
610             }
611             else {
612                 final ModuleInputConfiguration.Builder moduleInputConfigBuilder =
613                         new ModuleInputConfiguration.Builder();
614                 setModuleName(moduleInputConfigBuilder, inputFilePath, moduleName);
615                 setProperties(inputFilePath, module, moduleInputConfigBuilder);
616                 testInputConfigBuilder.addChildModule(moduleInputConfigBuilder.build());
617             }
618         }
619     }
620 
621     private static void handleKeyValueConfig(TestInputConfiguration.Builder testInputConfigBuilder,
622                                              String inputFilePath, List<String> lines)
623             throws CheckstyleException, IOException, ReflectiveOperationException {
624         int lineNo = 0;
625         while (lineNo < lines.size()) {
626             final ModuleInputConfiguration.Builder moduleInputConfigBuilder =
627                     new ModuleInputConfiguration.Builder();
628             final String moduleName = lines.get(lineNo);
629             setModuleName(moduleInputConfigBuilder, inputFilePath, moduleName);
630             setProperties(moduleInputConfigBuilder, inputFilePath, lines, lineNo + 1, moduleName);
631             testInputConfigBuilder.addChildModule(moduleInputConfigBuilder.build());
632             do {
633                 lineNo++;
634             } while (lineNo < lines.size()
635                     && lines.get(lineNo).isEmpty()
636                     || !lines.get(lineNo - 1).isEmpty());
637         }
638     }
639 
640     private static Map<String, String> getDefaultProperties(String fullyQualifiedClassName) {
641 
642         final Map<String, String> defaultProperties = new HashMap<>();
643 
644         if (PUBLIC_MODULE_DETAILS_MAP.isEmpty()) {
645             XmlMetaReader.readAllModulesIncludingThirdPartyIfAny().forEach(module -> {
646                 PUBLIC_MODULE_DETAILS_MAP.put(module.getFullQualifiedName(), module);
647             });
648         }
649 
650         final ModuleDetails moduleDetails = PUBLIC_MODULE_DETAILS_MAP.get(fullyQualifiedClassName);
651 
652         if (moduleDetails != null) {
653             defaultProperties.putAll(moduleDetails.getProperties().stream()
654                     .filter(prop -> prop.getName() != null)
655                     .collect(Collectors.toUnmodifiableMap(
656                         ModulePropertyDetails::getName,
657                         prop -> {
658                             final String value;
659                             if (prop.getDefaultValue() == null) {
660                                 value = "null";
661                             }
662                             else {
663                                 value = prop.getDefaultValue();
664                             }
665                             return value;
666                         }
667                     ))
668             );
669         }
670 
671         return defaultProperties;
672     }
673 
674     private static String getFullyQualifiedClassName(String filePath, String moduleName)
675             throws CheckstyleException {
676         String fullyQualifiedClassName;
677         if (MODULE_MAPPINGS.containsKey(moduleName)) {
678             fullyQualifiedClassName = MODULE_MAPPINGS.get(moduleName);
679         }
680         else if (moduleName.startsWith("com.")) {
681             fullyQualifiedClassName = moduleName;
682         }
683         else {
684             final String path = SLASH_PATTERN.matcher(filePath).replaceAll(".");
685             final int endIndex = path.lastIndexOf(moduleName.toLowerCase(Locale.ROOT));
686             if (endIndex == -1) {
687                 throw new CheckstyleException("Unable to resolve module name: " + moduleName
688                     + ". Please check for spelling errors or specify fully qualified class name.");
689             }
690             final int beginIndex = path.indexOf("com.puppycrawl");
691             fullyQualifiedClassName = path.substring(beginIndex, endIndex) + moduleName;
692             if (!fullyQualifiedClassName.endsWith("Filter")) {
693                 fullyQualifiedClassName += "Check";
694             }
695         }
696         return fullyQualifiedClassName;
697     }
698 
699     private static String getFilePath(String fileName, String inputFilePath) {
700         final int lastSlashIndex = Math.max(inputFilePath.lastIndexOf('\\'),
701                 inputFilePath.lastIndexOf('/'));
702         final String root = inputFilePath.substring(0, lastSlashIndex + 1);
703         return root + fileName;
704     }
705 
706     private static String getResourcePath(String fileName, String inputFilePath) {
707         final String filePath = getUriPath(fileName, inputFilePath);
708         final int lastSlashIndex = filePath.lastIndexOf('/');
709         final String root = filePath.substring(filePath.indexOf("puppycrawl") - 5,
710                 lastSlashIndex + 1);
711         return root + fileName;
712     }
713 
714     private static String getUriPath(String fileName, String inputFilePath) {
715         return new File(getFilePath(fileName, inputFilePath)).toURI().toString();
716     }
717 
718     private static String getResolvedPath(String fileValue, String inputFilePath) {
719         final String resolvedFilePath;
720 
721         if (fileValue.startsWith("(resource)")) {
722             resolvedFilePath =
723                     getResourcePath(fileValue.substring(fileValue.indexOf(')') + 1),
724                             inputFilePath);
725         }
726         else if (fileValue.startsWith("(uri)")) {
727             resolvedFilePath =
728                     getUriPath(fileValue.substring(fileValue.indexOf(')') + 1), inputFilePath);
729         }
730         else if (fileValue.contains("/") || fileValue.contains("\\")) {
731             resolvedFilePath = fileValue;
732         }
733         else {
734             resolvedFilePath = getFilePath(fileValue, inputFilePath);
735         }
736 
737         return resolvedFilePath;
738     }
739 
740     private static List<String> readFile(Path filePath) throws CheckstyleException {
741         try {
742             return Files.readAllLines(filePath);
743         }
744         catch (IOException exc) {
745             throw new CheckstyleException("Failed to read " + filePath, exc);
746         }
747     }
748 
749     private static void setModuleName(ModuleInputConfiguration.Builder moduleInputConfigBuilder,
750                                       String filePath, String moduleName)
751             throws CheckstyleException {
752         final String fullyQualifiedClassName = getFullyQualifiedClassName(filePath, moduleName);
753         moduleInputConfigBuilder.setModuleName(fullyQualifiedClassName);
754     }
755 
756     private static String toStringConvertForArrayValue(Object value) {
757         String result = NULL_STRING;
758 
759         if (value instanceof double[] arr) {
760             result = Arrays.stream(arr)
761                            .boxed()
762                            .map(number -> {
763                                return BigDecimal.valueOf(number)
764                                                 .stripTrailingZeros()
765                                                 .toPlainString();
766                            })
767                            .collect(Collectors.joining(","));
768         }
769         else if (value instanceof int[] ints) {
770             result = Arrays.toString(ints).replaceAll("[\\[\\]\\s]", "");
771         }
772         else if (value instanceof boolean[] booleans) {
773             result = Arrays.toString(booleans).replaceAll("[\\[\\]\\s]", "");
774         }
775         else if (value instanceof long[] longs) {
776             result = Arrays.toString(longs).replaceAll("[\\[\\]\\s]", "");
777         }
778         else if (value instanceof Object[] objects) {
779             result = Arrays.toString(objects).replaceAll("[\\[\\]\\s]", "");
780         }
781         return result;
782     }
783 
784     /**
785      * Validate default value.
786      *
787      * @param propertyName the property name.
788      * @param propertyDefaultValue the specified default value in the file.
789      * @param fullyQualifiedModuleName the fully qualified module name.
790      */
791     private static void validateDefault(String propertyName,
792                                            String propertyDefaultValue,
793                                            String fullyQualifiedModuleName)
794             throws ReflectiveOperationException {
795         final Object checkInstance = createCheckInstance(fullyQualifiedModuleName);
796         final Object actualDefault;
797         final Class<?> propertyType;
798         final String actualDefaultAsString;
799 
800         if ("tokens".equals(propertyName)) {
801             actualDefault = TestUtil.invokeMethod(checkInstance,
802                     "getDefaultTokens", Object.class);
803             propertyType = actualDefault.getClass();
804             final int[] arr = (int[]) actualDefault;
805             actualDefaultAsString = Arrays.stream(arr)
806                                           .mapToObj(TokenUtil::getTokenName)
807                                           .collect(Collectors.joining(", "));
808         }
809         else if ("javadocTokens".equals(propertyName)) {
810             actualDefault = TestUtil.invokeMethod(checkInstance,
811                     "getDefaultJavadocTokens", Object.class);
812             propertyType = actualDefault.getClass();
813             final int[] arr = (int[]) actualDefault;
814             actualDefaultAsString = Arrays.stream(arr)
815                                           .mapToObj(JavadocUtil::getTokenName)
816                                           .collect(Collectors.joining(", "));
817         }
818         else {
819             actualDefault = getPropertyDefaultValue(checkInstance, propertyName);
820             if (actualDefault == null) {
821                 propertyType = null;
822             }
823             else {
824                 propertyType = actualDefault.getClass();
825             }
826             actualDefaultAsString = convertDefaultValueToString(actualDefault);
827         }
828         if (!isDefaultValue(propertyDefaultValue, actualDefaultAsString, propertyType)) {
829             final String message = String.format(Locale.ROOT,
830                     "Default value mismatch for %s in %s: specified '%s' but actually is '%s'",
831                     propertyName, fullyQualifiedModuleName,
832                     propertyDefaultValue, actualDefaultAsString);
833             throw new IllegalArgumentException(message);
834         }
835     }
836 
837     private static boolean isCollectionValues(String specifiedDefault, String actualDefault) {
838         final Set<String> specifiedSet = new HashSet<>(
839             Arrays.asList(specifiedDefault.replaceAll("[\\[\\]\\s]", "").split(",")));
840         final Set<String> actualSet = new HashSet<>(
841             Arrays.asList(actualDefault.replaceAll("[\\[\\]\\s]", "").split(",")));
842         return actualSet.equals(specifiedSet);
843     }
844 
845     private static String convertDefaultValueToString(Object value) {
846         final String defaultValueAsString;
847         if (value == null) {
848             defaultValueAsString = NULL_STRING;
849         }
850         else if (value instanceof String strValue) {
851             defaultValueAsString = toStringForStringValue(strValue);
852         }
853         else if (value.getClass().isArray()) {
854             defaultValueAsString = toStringConvertForArrayValue(value);
855         }
856         else if (value instanceof BitSet set) {
857             defaultValueAsString = toStringForBitSetValue(set);
858         }
859         else if (value instanceof Collection<?> values) {
860             defaultValueAsString = toStringForCollectionValue(values);
861         }
862         else {
863             defaultValueAsString = String.valueOf(value);
864         }
865         return defaultValueAsString;
866     }
867 
868     private static String toStringForStringValue(String strValue) {
869         final String str;
870         if (strValue.startsWith("(") && strValue.endsWith(")")) {
871             str = strValue.substring(1, strValue.length() - 1);
872         }
873         else {
874             str = strValue;
875         }
876         return str;
877     }
878 
879     private static String toStringForBitSetValue(BitSet bitSet) {
880         return bitSet.stream()
881                      .mapToObj(TokenUtil::getTokenName)
882                      .collect(Collectors.joining(","));
883     }
884 
885     private static String toStringForCollectionValue(Collection<?> collection) {
886         return collection.toString().replaceAll("[\\[\\]\\s]", "");
887     }
888 
889     /**
890      * Validate default value.
891      *
892      * @param propertyDefaultValue the specified default value in the file.
893      * @param actualDefault the actual default value
894      * @param fieldType the data type of default value.
895      */
896     private static boolean isDefaultValue(final String propertyDefaultValue,
897                                           final String actualDefault,
898                                           final Class<?> fieldType) {
899         final boolean result;
900 
901         if (NULL_STRING.equals(actualDefault)) {
902             result = isNull(propertyDefaultValue);
903         }
904         else if (isNumericType(fieldType)) {
905             final BigDecimal specified = new BigDecimal(propertyDefaultValue);
906             final BigDecimal actual = new BigDecimal(actualDefault);
907             result = specified.compareTo(actual) == 0;
908         }
909         else if (fieldType.isArray()
910             || Collection.class.isAssignableFrom(fieldType)
911             || BitSet.class.isAssignableFrom(fieldType)) {
912             result = isCollectionValues(propertyDefaultValue, actualDefault);
913         }
914         else if (fieldType.isEnum() || fieldType.isLocalClass()) {
915             result = propertyDefaultValue.equalsIgnoreCase(actualDefault);
916         }
917         else {
918             result = propertyDefaultValue.equals(actualDefault);
919         }
920         return result;
921     }
922 
923     private static Object createCheckInstance(String className) throws
924             ReflectiveOperationException {
925         final Class<?> checkClass = Class.forName(className);
926         return TestUtil.instantiate(checkClass);
927     }
928 
929     private static String readPropertiesContent(int beginLineNo, List<String> lines) {
930         final StringBuilder stringBuilder = new StringBuilder(128);
931         int lineNo = beginLineNo;
932         String line = lines.get(lineNo);
933         while (!line.isEmpty() && !"*/".equals(line)) {
934             stringBuilder.append(line).append('\n');
935             lineNo++;
936             line = lines.get(lineNo);
937         }
938         return stringBuilder.toString();
939     }
940 
941     private static void validateProperties(Map<String, String> propertiesWithMissingDefaultTag,
942             List<String> unusedProperties) throws CheckstyleException {
943 
944         if (!propertiesWithMissingDefaultTag.isEmpty()) {
945 
946             final String propertiesList = propertiesWithMissingDefaultTag.entrySet().stream()
947                     .map(entry -> {
948                         return String.format(Locale.ROOT, "%s = (default)%s",
949                                 entry.getKey(), entry.getValue());
950                     })
951                     .collect(Collectors.joining(", "));
952 
953             final String message = String.format(Locale.ROOT,
954                     "Default properties must use the '(default)' tag."
955                     + " Properties missing the '(default)' tag: %s", propertiesList);
956             throw new CheckstyleException(message);
957         }
958         if (!unusedProperties.isEmpty()) {
959             final String message = String.format(Locale.ROOT,
960                     "All properties must be explicitly specified."
961                     + " Found unused properties: %s", unusedProperties);
962             throw new CheckstyleException(message);
963         }
964     }
965 
966     private static void validateDefaultProperties(
967         Map<Object, Object> actualProperties,
968         Map<String, String> defaultProperties) throws CheckstyleException {
969 
970         final Map<String, String> propertiesWithMissingDefaultTag = actualProperties
971                 .entrySet().stream()
972                 .filter(entry -> !"id".equals(entry.getKey().toString()))
973                 .filter(entry -> !"tabWidth".equals(entry.getKey().toString()))
974                 .filter(entry -> !"severity".equals(entry.getKey().toString()))
975                 .filter(entry -> !entry.getKey().toString().startsWith("message."))
976                 .filter(entry -> !entry.getValue().toString().startsWith("(default)"))
977                 .filter(entry -> {
978                     return defaultProperties
979                             .get(entry.getKey().toString())
980                             .equals(entry.getValue().toString());
981                 })
982                 .collect(HashMap::new,
983                         (map, entry) -> {
984                         map.put(entry.getKey().toString(), entry.getValue().toString());
985                     }, HashMap::putAll);
986         final List<String> unusedProperties = defaultProperties.keySet().stream()
987                 .filter(propertyName -> !actualProperties.containsKey(propertyName))
988                 .toList();
989 
990         validateProperties(propertiesWithMissingDefaultTag, unusedProperties);
991     }
992 
993     private static void setProperties(String inputFilePath, Configuration module,
994                                       ModuleInputConfiguration.Builder moduleInputConfigBuilder)
995             throws CheckstyleException {
996         final String[] getPropertyNames = module.getPropertyNames();
997         for (final String propertyName : getPropertyNames) {
998             final String propertyValue = module.getProperty(propertyName);
999 
1000             if ("file".equals(propertyName)) {
1001                 final String filePath = getResolvedPath(propertyValue, inputFilePath);
1002                 moduleInputConfigBuilder.addNonDefaultProperty(propertyName, filePath);
1003             }
1004             else {
1005                 if (NULL_STRING.equals(propertyValue)) {
1006                     moduleInputConfigBuilder.addNonDefaultProperty(propertyName, null);
1007                 }
1008                 else {
1009                     moduleInputConfigBuilder.addNonDefaultProperty(propertyName, propertyValue);
1010                 }
1011             }
1012         }
1013 
1014         final Map<String, String> messages = module.getMessages();
1015         for (final Map.Entry<String, String> entry : messages.entrySet()) {
1016             final String key = entry.getKey();
1017             final String value = entry.getValue();
1018             moduleInputConfigBuilder.addModuleMessage(key, value);
1019         }
1020     }
1021 
1022     private static void setProperties(ModuleInputConfiguration.Builder inputConfigBuilder,
1023                             String inputFilePath,
1024                             List<String> lines,
1025                             int beginLineNo, String moduleName)
1026             throws IOException, CheckstyleException, ReflectiveOperationException {
1027 
1028         final String propertyContent = readPropertiesContent(beginLineNo, lines);
1029         final Map<Object, Object> properties = loadProperties(propertyContent);
1030         final String fullyQualifiedClassName =
1031                 getFullyQualifiedClassName(inputFilePath, moduleName);
1032 
1033         final boolean isSuppressedValidateDefaultFile = SUPPRESSED_VALIDATE_DEFAULT_FILES.stream()
1034                 .anyMatch(Path.of(inputFilePath)::endsWith);
1035 
1036         if (!isSuppressedValidateDefaultFile) {
1037             validateDefaultProperties(properties, getDefaultProperties(fullyQualifiedClassName));
1038         }
1039 
1040         for (final Map.Entry<Object, Object> entry : properties.entrySet()) {
1041             final String key = entry.getKey().toString();
1042             final String value = entry.getValue().toString();
1043 
1044             if (key.startsWith("message.")) {
1045                 inputConfigBuilder.addModuleMessage(key.substring(8), value);
1046             }
1047             else if (NULL_STRING.equals(value)) {
1048                 inputConfigBuilder.addNonDefaultProperty(key, null);
1049             }
1050             else if (value.startsWith("(file)")) {
1051                 final String fileName = value.substring(value.indexOf(')') + 1);
1052                 final String filePath = getResolvedPath(fileName, inputFilePath);
1053                 inputConfigBuilder.addNonDefaultProperty(key, filePath);
1054             }
1055             else if (value.startsWith("(default)")) {
1056                 final String defaultValue = value.substring(value.indexOf(')') + 1);
1057                 if (!isSuppressedValidateDefaultFile) {
1058                     validateDefault(key, defaultValue, fullyQualifiedClassName);
1059                 }
1060                 if (NULL_STRING.equals(defaultValue)) {
1061                     inputConfigBuilder.addDefaultProperty(key, null);
1062                 }
1063                 else {
1064                     inputConfigBuilder.addDefaultProperty(key, defaultValue);
1065                 }
1066             }
1067             else {
1068                 inputConfigBuilder.addNonDefaultProperty(key, value);
1069             }
1070         }
1071     }
1072 
1073     private static boolean shouldSpecifyViolationMessage(
1074             TestInputConfiguration.Builder inputConfigBuilder, String inputFilePath) {
1075 
1076         boolean result = false;
1077 
1078         final List<ModuleInputConfiguration> moduleLists =
1079                 inputConfigBuilder.getChildrenModules();
1080 
1081         final boolean isSuppressedFile = SUPPRESSED_VALIDATE_MESSAGE_FILES.stream()
1082                 .anyMatch(Path.of(inputFilePath)::endsWith);
1083 
1084         if (!isSuppressedFile && moduleLists.size() == 1) {
1085             final String moduleName = moduleLists.getFirst().getModuleName();
1086 
1087             if (!PERMANENT_SUPPRESSED_CHECKS.contains(moduleName)
1088                     && !SUPPRESSED_CHECKS.contains(moduleName)) {
1089                 result = true;
1090             }
1091         }
1092 
1093         return result;
1094     }
1095 
1096     private static void setViolations(TestInputConfiguration.Builder inputConfigBuilder,
1097                                       List<String> lines,
1098                                       boolean useFilteredViolations,
1099                                       String inputFilePath)
1100             throws CheckstyleException {
1101 
1102         final boolean specifyViolationMessage =
1103                 shouldSpecifyViolationMessage(inputConfigBuilder, inputFilePath);
1104 
1105         for (int lineNo = 0; lineNo < lines.size(); lineNo++) {
1106             setViolations(inputConfigBuilder, lines,
1107                     useFilteredViolations, lineNo, specifyViolationMessage);
1108         }
1109     }
1110 
1111     /**
1112      * Sets the violations.
1113      *
1114      * @param inputConfigBuilder the input file path.
1115      * @param lines all the lines in the file.
1116      * @param useFilteredViolations flag to set filtered violations.
1117      * @param lineNo current line.
1118      * @noinspection IfStatementWithTooManyBranches
1119      * @noinspectionreason IfStatementWithTooManyBranches - complex logic of violation
1120      *      parser requires giant if/else
1121      * @throws CheckstyleException if violation message is not specified
1122      */
1123     // -@cs[JavaNCSS|CyclomaticComplexity] splitting this method is not reasonable.
1124     // -@cs[MethodLength|ExecutableStatementCount] splitting this method is not reasonable.
1125     private static void setViolations(TestInputConfiguration.Builder inputConfigBuilder,
1126                                       List<String> lines, boolean useFilteredViolations,
1127                                       int lineNo, boolean specifyViolationMessage)
1128             throws CheckstyleException {
1129         final String line = lines.get(lineNo);
1130         if (ANY_OK_VIOLATION_PATTERN.matcher(line).matches()
1131                 && !ALLOWED_OK_VIOLATION_PATTERN.matcher(line).matches()) {
1132             throw new CheckstyleException(
1133                     "Invalid format (must be \"// ok...\" or \"// violation...\"): " + line);
1134         }
1135 
1136         final Matcher violationMatcher =
1137                 VIOLATION_PATTERN.matcher(line);
1138         final Matcher violationAboveMatcher =
1139                 VIOLATION_ABOVE_PATTERN.matcher(line);
1140         final Matcher violationBelowMatcher =
1141                 VIOLATION_BELOW_PATTERN.matcher(line);
1142         final Matcher violationAboveWithExplanationMatcher =
1143                 VIOLATION_ABOVE_WITH_EXPLANATION_PATTERN.matcher(line);
1144         final Matcher violationBelowWithExplanationMatcher =
1145                 VIOLATION_BELOW_WITH_EXPLANATION_PATTERN.matcher(line);
1146         final Matcher violationWithExplanationMatcher =
1147                 VIOLATION_WITH_EXPLANATION_PATTERN.matcher(line);
1148         final Matcher violationSomeLinesAboveMatcher =
1149                 VIOLATION_SOME_LINES_ABOVE_PATTERN.matcher(line);
1150         final Matcher violationSomeLinesBelowMatcher =
1151                 VIOLATION_SOME_LINES_BELOW_PATTERN.matcher(line);
1152         final Matcher violationFirstLineMatcher =
1153                 VIOLATION_FIRST_LINE_PATTERN.matcher(line);
1154         final Matcher violationLastLineMatcher =
1155                 VIOLATION_LAST_LINE_PATTERN.matcher(line);
1156 
1157         if (violationMatcher.matches()) {
1158             final String violationMessage =
1159                     extractMessage(violationMatcher.group(1), lines, lineNo);
1160             final int violationLineNum = lineNo + 1;
1161             checkWhetherViolationSpecified(specifyViolationMessage, violationMessage,
1162                     violationLineNum);
1163             inputConfigBuilder.addViolation(violationLineNum, violationMessage);
1164         }
1165         else if (violationAboveMatcher.matches()) {
1166             final String violationMessage =
1167                     extractMessage(violationAboveMatcher.group(1), lines, lineNo);
1168             checkWhetherViolationSpecified(specifyViolationMessage, violationMessage, lineNo);
1169             inputConfigBuilder.addViolation(lineNo, violationMessage);
1170         }
1171         else if (violationBelowMatcher.matches()) {
1172             final String violationMessage =
1173                     extractMessage(violationBelowMatcher.group(1), lines, lineNo);
1174             final int violationLineNum = lineNo + 2;
1175             checkWhetherViolationSpecified(specifyViolationMessage, violationMessage,
1176                     violationLineNum);
1177             inputConfigBuilder.addViolation(violationLineNum, violationMessage);
1178         }
1179         else if (violationAboveWithExplanationMatcher.matches()) {
1180             final String violationMessage =
1181                     extractMessage(violationAboveWithExplanationMatcher.group(1), lines, lineNo);
1182             checkWhetherViolationSpecified(specifyViolationMessage, violationMessage, lineNo);
1183             inputConfigBuilder.addViolation(lineNo, violationMessage);
1184         }
1185         else if (violationBelowWithExplanationMatcher.matches()) {
1186             final String violationMessage =
1187                     extractMessage(violationBelowWithExplanationMatcher.group(1), lines, lineNo);
1188             final int violationLineNum = lineNo + 2;
1189             checkWhetherViolationSpecified(specifyViolationMessage, violationMessage,
1190                     violationLineNum);
1191             inputConfigBuilder.addViolation(violationLineNum, violationMessage);
1192         }
1193         else if (violationWithExplanationMatcher.matches()) {
1194             final int violationLineNum = lineNo + 1;
1195             checkWhetherViolationSpecified(specifyViolationMessage, null, violationLineNum);
1196             inputConfigBuilder.addViolation(violationLineNum, null);
1197         }
1198         else if (violationSomeLinesAboveMatcher.matches()) {
1199             final String violationMessage =
1200                     extractMessage(violationSomeLinesAboveMatcher.group(2), lines, lineNo);
1201             final int linesAbove = Integer.parseInt(violationSomeLinesAboveMatcher.group(1)) - 1;
1202             final int violationLineNum = lineNo - linesAbove;
1203             checkWhetherViolationSpecified(specifyViolationMessage, violationMessage,
1204                     violationLineNum);
1205             inputConfigBuilder.addViolation(violationLineNum, violationMessage);
1206         }
1207         else if (violationSomeLinesBelowMatcher.matches()) {
1208             final String violationMessage =
1209                     extractMessage(violationSomeLinesBelowMatcher.group(2), lines, lineNo);
1210             final int linesBelow = Integer.parseInt(violationSomeLinesBelowMatcher.group(1)) + 1;
1211             final int violationLineNum = lineNo + linesBelow;
1212             checkWhetherViolationSpecified(specifyViolationMessage, violationMessage,
1213                     violationLineNum);
1214             inputConfigBuilder.addViolation(violationLineNum, violationMessage);
1215         }
1216         else if (violationFirstLineMatcher.matches()) {
1217             final String violationMessage =
1218                     extractMessage(violationFirstLineMatcher.group(1), lines, lineNo);
1219             checkWhetherViolationSpecified(specifyViolationMessage, violationMessage, 1);
1220             inputConfigBuilder.addViolation(1, violationMessage);
1221         }
1222         else if (violationLastLineMatcher.matches()) {
1223             final String violationMessage =
1224                     extractMessage(violationLastLineMatcher.group(1), lines, lineNo);
1225             final int lastLineNum = lines.size();
1226             checkWhetherViolationSpecified(specifyViolationMessage, violationMessage,
1227                     lastLineNum);
1228             inputConfigBuilder.addViolation(lastLineNum, violationMessage);
1229         }
1230         else {
1231             setViolationsForMultipleAndFiltered(inputConfigBuilder, lines,
1232                     useFilteredViolations, lineNo, specifyViolationMessage);
1233         }
1234     }
1235 
1236     /**
1237      * Sets violations for multiple-violation patterns, grouped patterns, and filtered violations.
1238      *
1239      * @param inputConfigBuilder the builder to add violations to.
1240      * @param lines all the lines in the file.
1241      * @param useFilteredViolations flag to set filtered violations.
1242      * @param lineNo current line number.
1243      * @param specifyViolationMessage whether violation message must be specified.
1244      * @throws CheckstyleException if violation message is not specified.
1245      */
1246     private static void setViolationsForMultipleAndFiltered(
1247             TestInputConfiguration.Builder inputConfigBuilder,
1248             List<String> lines, boolean useFilteredViolations,
1249             int lineNo, boolean specifyViolationMessage)
1250             throws CheckstyleException {
1251         final String line = lines.get(lineNo);
1252 
1253         boolean matched = isRelativeLineViolationProcessed(inputConfigBuilder, lines, line, lineNo,
1254                 specifyViolationMessage);
1255 
1256         if (!matched) {
1257             matched = isMultipleViolationProcessed(inputConfigBuilder, line, lineNo,
1258                     specifyViolationMessage);
1259         }
1260 
1261         if (!matched) {
1262             if (useFilteredViolations) {
1263                 setFilteredViolation(inputConfigBuilder, lineNo + 1,
1264                         lines, lineNo, specifyViolationMessage);
1265             }
1266             else if (!isFilteredViolationComment(line)) {
1267                 final Matcher violationsDefault = VIOLATION_DEFAULT.matcher(line);
1268                 if (violationsDefault.matches()) {
1269                     final int violationLineNum = lineNo + 1;
1270                     checkWhetherViolationSpecified(specifyViolationMessage, null, violationLineNum);
1271                     inputConfigBuilder.addViolation(violationLineNum, null);
1272                 }
1273             }
1274         }
1275     }
1276 
1277     private static boolean isRelativeLineViolationProcessed(
1278             TestInputConfiguration.Builder inputConfigBuilder, List<String> lines,
1279             String line, int lineNo, boolean specifyViolationMessage) throws CheckstyleException {
1280         final Matcher violationsAboveMatcherWithMessages =
1281                 VIOLATIONS_ABOVE_PATTERN_WITH_MESSAGES.matcher(line);
1282         final Matcher violationsSomeLinesAboveMatcher =
1283                 VIOLATIONS_SOME_LINES_ABOVE_PATTERN.matcher(line);
1284         final Matcher violationsSomeLinesBelowMatcher =
1285                 VIOLATIONS_SOME_LINES_BELOW_PATTERN.matcher(line);
1286 
1287         boolean processed = true;
1288         if (violationsAboveMatcherWithMessages.matches()) {
1289             inputConfigBuilder.addViolations(
1290                     getExpectedViolationsForSpecificLine(
1291                             lines, lineNo, lineNo, violationsAboveMatcherWithMessages,
1292                             specifyViolationMessage));
1293         }
1294         else if (violationsSomeLinesAboveMatcher.matches()) {
1295             inputConfigBuilder.addViolations(
1296                     getExpectedViolations(
1297                             lines, lineNo, violationsSomeLinesAboveMatcher, true,
1298                             specifyViolationMessage));
1299         }
1300         else if (violationsSomeLinesBelowMatcher.matches()) {
1301             inputConfigBuilder.addViolations(
1302                     getExpectedViolations(
1303                             lines, lineNo, violationsSomeLinesBelowMatcher, false,
1304                             specifyViolationMessage));
1305         }
1306         else {
1307             processed = false;
1308         }
1309         return processed;
1310     }
1311 
1312     private static boolean isMultipleViolationProcessed(
1313             TestInputConfiguration.Builder inputConfigBuilder, String line,
1314             int lineNo, boolean specifyViolationMessage) throws CheckstyleException {
1315         final Matcher multipleViolationsMatcher = MULTIPLE_VIOLATIONS_PATTERN.matcher(line);
1316         final Matcher multipleViolationsAboveMatcher =
1317                 MULTIPLE_VIOLATIONS_ABOVE_PATTERN.matcher(line);
1318         final Matcher multipleViolationsBelowMatcher =
1319                 MULTIPLE_VIOLATIONS_BELOW_PATTERN.matcher(line);
1320 
1321         boolean processed = true;
1322         if (multipleViolationsMatcher.matches()) {
1323             final int violationLineNum = lineNo + 1;
1324             final int count = Integer.parseInt(multipleViolationsMatcher.group(1));
1325             checkWhetherViolationSpecified(specifyViolationMessage, null, violationLineNum);
1326             Collections.nCopies(count, violationLineNum)
1327                     .forEach(actualLineNumber -> {
1328                         inputConfigBuilder.addViolation(actualLineNumber, null);
1329                     });
1330         }
1331         else if (multipleViolationsAboveMatcher.matches()) {
1332             final int count = Integer.parseInt(multipleViolationsAboveMatcher.group(1));
1333             checkWhetherViolationSpecified(specifyViolationMessage, null, lineNo);
1334             Collections.nCopies(count, lineNo)
1335                     .forEach(actualLineNumber -> {
1336                         inputConfigBuilder.addViolation(actualLineNumber, null);
1337                     });
1338         }
1339         else if (multipleViolationsBelowMatcher.matches()) {
1340             final int violationLineNum = lineNo + 2;
1341             final int count = Integer.parseInt(multipleViolationsBelowMatcher.group(1));
1342             checkWhetherViolationSpecified(specifyViolationMessage, null, violationLineNum);
1343             Collections.nCopies(count, violationLineNum)
1344                     .forEach(actualLineNumber -> {
1345                         inputConfigBuilder.addViolation(actualLineNumber, null);
1346                     });
1347         }
1348         else {
1349             processed = false;
1350         }
1351         return processed;
1352     }
1353 
1354     private static List<TestInputViolation> getExpectedViolationsForSpecificLine(
1355             List<String> lines, int lineNo, int violationLineNum,
1356             Matcher matcher, boolean specifyViolationMessage) throws CheckstyleException {
1357         final List<TestInputViolation> results = new ArrayList<>();
1358 
1359         final int expectedMessageCount =
1360             Integer.parseInt(matcher.group(1));
1361         for (int index = 1; index <= expectedMessageCount; index++) {
1362             final String lineWithMessage = lines.get(lineNo + index);
1363             final Matcher messageMatcher = VIOLATION_MESSAGE_PATTERN.matcher(lineWithMessage);
1364             if (messageMatcher.find()) {
1365                 final String violationMessage = messageMatcher.group(1);
1366                 checkWhetherViolationSpecified(specifyViolationMessage, violationMessage,
1367                         violationLineNum);
1368                 results.add(new TestInputViolation(violationLineNum, violationMessage));
1369             }
1370         }
1371         if (results.size() != expectedMessageCount) {
1372             final String message = String.format(Locale.ROOT,
1373                 "Declared amount of violation messages at line %s is %s but found %s",
1374                 lineNo + 1, expectedMessageCount, results.size());
1375             throw new IllegalStateException(message);
1376         }
1377         return results;
1378     }
1379 
1380     private static List<TestInputViolation> getExpectedViolations(
1381             List<String> lines, int lineNo,
1382             Matcher matcher, boolean isAbove, boolean specifyViolationMessage)
1383                     throws CheckstyleException {
1384         final int violationLine =
1385             Integer.parseInt(matcher.group(2));
1386         final int violationLineNum;
1387         if (isAbove) {
1388             violationLineNum = lineNo - violationLine + 1;
1389         }
1390         else {
1391             violationLineNum = lineNo + violationLine + 1;
1392         }
1393         return getExpectedViolationsForSpecificLine(lines,
1394             lineNo, violationLineNum, matcher, specifyViolationMessage);
1395     }
1396 
1397     private static void setFilteredViolation(TestInputConfiguration.Builder inputConfigBuilder,
1398                                              int lineNo, List<String> lines,
1399                                              int currentLineNo,
1400                                              boolean specifyViolationMessage)
1401             throws CheckstyleException {
1402         final String line = lines.get(currentLineNo);
1403         final Matcher violationMatcher =
1404                 FILTERED_VIOLATION_PATTERN.matcher(line);
1405         final Matcher violationAboveMatcher =
1406                 FILTERED_VIOLATION_ABOVE_PATTERN.matcher(line);
1407         final Matcher violationBelowMatcher =
1408                 FILTERED_VIOLATION_BELOW_PATTERN.matcher(line);
1409         final Matcher violationSomeLinesAboveMatcher =
1410                 FILTERED_VIOLATION_SOME_LINES_ABOVE_PATTERN.matcher(line);
1411         final Matcher violationSomeLinesBelowMatcher =
1412                 FILTERED_VIOLATION_SOME_LINES_BELOW_PATTERN.matcher(line);
1413         if (violationMatcher.matches()) {
1414             final String violationMessage =
1415                     extractMessage(violationMatcher.group(1), lines, currentLineNo);
1416             checkWhetherViolationSpecified(specifyViolationMessage, violationMessage, lineNo);
1417             inputConfigBuilder.addFilteredViolation(lineNo, violationMessage);
1418         }
1419         else if (violationAboveMatcher.matches()) {
1420             final String violationMessage =
1421                     extractMessage(violationAboveMatcher.group(1), lines, currentLineNo);
1422             final int violationLineNum = lineNo - 1;
1423             checkWhetherViolationSpecified(specifyViolationMessage, violationMessage,
1424                     violationLineNum);
1425             inputConfigBuilder.addFilteredViolation(violationLineNum, violationMessage);
1426         }
1427         else if (violationBelowMatcher.matches()) {
1428             final String violationMessage =
1429                     extractMessage(violationBelowMatcher.group(1), lines, currentLineNo);
1430             final int violationLineNum = lineNo + 1;
1431             checkWhetherViolationSpecified(specifyViolationMessage, violationMessage,
1432                     violationLineNum);
1433             inputConfigBuilder.addFilteredViolation(violationLineNum, violationMessage);
1434         }
1435         else if (violationSomeLinesAboveMatcher.matches()) {
1436             final String violationMessage =
1437                     extractMessage(violationSomeLinesAboveMatcher.group(2), lines, currentLineNo);
1438             final int linesAbove = Integer.parseInt(violationSomeLinesAboveMatcher.group(1));
1439             final int violationLineNum = lineNo - linesAbove;
1440             checkWhetherViolationSpecified(specifyViolationMessage, violationMessage,
1441                     violationLineNum);
1442             inputConfigBuilder.addFilteredViolation(violationLineNum, violationMessage);
1443         }
1444         else if (violationSomeLinesBelowMatcher.matches()) {
1445             final String violationMessage =
1446                     extractMessage(violationSomeLinesBelowMatcher.group(2), lines, currentLineNo);
1447             final int linesBelow = Integer.parseInt(violationSomeLinesBelowMatcher.group(1));
1448             final int violationLineNum = lineNo + linesBelow;
1449             checkWhetherViolationSpecified(specifyViolationMessage, violationMessage,
1450                     violationLineNum);
1451             inputConfigBuilder.addFilteredViolation(violationLineNum, violationMessage);
1452         }
1453     }
1454 
1455     private static boolean isViolationComment(String line) {
1456         return VIOLATION_PATTERN.matcher(line).matches()
1457                 || VIOLATION_ABOVE_PATTERN.matcher(line).matches()
1458                 || VIOLATION_BELOW_PATTERN.matcher(line).matches()
1459                 || VIOLATION_SOME_LINES_ABOVE_PATTERN.matcher(line).matches()
1460                 || VIOLATION_SOME_LINES_BELOW_PATTERN.matcher(line).matches()
1461                 || VIOLATION_FIRST_LINE_PATTERN.matcher(line).matches()
1462                 || VIOLATION_LAST_LINE_PATTERN.matcher(line).matches()
1463                 || isFilteredViolationComment(line);
1464     }
1465 
1466     /**
1467      * Checks whether the given line is a well-formed "filtered violation" comment,
1468      * in any of its recognized forms (bare, above, below, N-lines-above, N-lines-below).
1469      *
1470      * @param line the line to check.
1471      * @return true if the line matches any filtered-violation comment pattern.
1472      */
1473     private static boolean isFilteredViolationComment(String line) {
1474         return FILTERED_VIOLATION_PATTERN.matcher(line).matches()
1475                 || FILTERED_VIOLATION_ABOVE_PATTERN.matcher(line).matches()
1476                 || FILTERED_VIOLATION_BELOW_PATTERN.matcher(line).matches()
1477                 || FILTERED_VIOLATION_SOME_LINES_ABOVE_PATTERN.matcher(line).matches()
1478                 || FILTERED_VIOLATION_SOME_LINES_BELOW_PATTERN.matcher(line).matches();
1479     }
1480 
1481     private static String assembleTripleQuoteMessage(String firstPart,
1482                                                      List<String> lines, int startLineNo) {
1483         final String result;
1484         if (firstPart.endsWith("\"\"")) {
1485             result = firstPart.substring(0, firstPart.length() - 2);
1486         }
1487         else {
1488             final StringBuilder builder = new StringBuilder(firstPart.strip());
1489             int nextLine = startLineNo + 1;
1490             boolean done = false;
1491             while (!done && nextLine < lines.size()) {
1492                 final String candidate = lines.get(nextLine);
1493                 final Matcher continuation =
1494                         MULTILINE_CONTINUATION_PATTERN.matcher(candidate);
1495 
1496                 final boolean isViolation = isViolationComment(candidate);
1497                 final boolean isContinuation = continuation.matches();
1498 
1499                 if (!isViolation && isContinuation) {
1500                     final String part = continuation.group(1);
1501                     if (part.contains(TRIPLE_QUOTE)) {
1502                         final int idx = part.indexOf(TRIPLE_QUOTE);
1503                         final String lastPart = part.substring(0, idx).trim();
1504                         if (!lastPart.isEmpty()) {
1505                             builder.append(' ').append(lastPart);
1506                         }
1507                         done = true;
1508                     }
1509                     else {
1510                         builder.append(' ').append(part.strip());
1511                         nextLine++;
1512                     }
1513                 }
1514                 else {
1515                     done = true;
1516                 }
1517             }
1518             result = builder.toString();
1519         }
1520         return result.replaceAll("\\s+", " ").trim();
1521     }
1522 
1523     private static String extractMessage(String rawMessage,
1524                                          List<String> lines, int lineNo) {
1525         String result = null;
1526 
1527         if (rawMessage != null) {
1528             if (rawMessage.startsWith("\"\"")) {
1529                 result = assembleTripleQuoteMessage(
1530                         rawMessage.substring(2), lines, lineNo);
1531             }
1532             else {
1533                 // Strip trailing quote left by relaxed pattern for single-line messages
1534                 if (rawMessage.endsWith("'") || rawMessage.endsWith("\"")) {
1535                     result = rawMessage.substring(0, rawMessage.length() - 1);
1536                 }
1537                 else {
1538                     result = rawMessage;
1539                 }
1540             }
1541         }
1542 
1543         return result;
1544     }
1545 
1546     /**
1547      * Check whether violation is specified along with {@code // violation} comment.
1548      *
1549      * @param shouldViolationMsgBeSpecified should violation messages be specified.
1550      * @param violationMessage violation message
1551      * @param lineNum line number
1552      * @throws CheckstyleException if violation message is not specified
1553      */
1554     private static void checkWhetherViolationSpecified(boolean shouldViolationMsgBeSpecified,
1555             String violationMessage, int lineNum) throws CheckstyleException {
1556         if (shouldViolationMsgBeSpecified && violationMessage == null) {
1557             throw new CheckstyleException(
1558                     "Violation message should be specified on line " + lineNum);
1559         }
1560     }
1561 
1562     private static Map<Object, Object> loadProperties(String propertyContent) throws IOException {
1563         final Properties properties = new Properties();
1564         properties.load(new StringReader(propertyContent));
1565         return properties;
1566     }
1567 
1568     private static boolean isNumericType(Class<?> fieldType) {
1569         return Number.class.isAssignableFrom(fieldType)
1570                 || fieldType.equals(int.class)
1571                 || fieldType.equals(double.class)
1572                 || fieldType.equals(long.class)
1573                 || fieldType.equals(float.class);
1574     }
1575 
1576     public static Object getPropertyDefaultValue(Object checkInstance,
1577                                                  String propertyName) {
1578         Object retVal;
1579         try {
1580             retVal = TestUtil.getInternalState(checkInstance, propertyName, Object.class);
1581         }
1582         catch (IllegalStateException exc) {
1583             retVal = null;
1584         }
1585         return retVal;
1586     }
1587 
1588     private static boolean isNull(String propertyDefaultValue) {
1589         return NULL_STRING.equals(propertyDefaultValue)
1590                 || propertyDefaultValue.isEmpty()
1591                 || "null".equals(propertyDefaultValue)
1592                 || "\"\"".equals(propertyDefaultValue);
1593     }
1594 
1595 }