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