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