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