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.TodoCommentCheck",
325             "com.puppycrawl.tools.checkstyle.checks.blocks.LeftCurlyCheck",
326             "com.puppycrawl.tools.checkstyle.checks.blocks.NeedBracesCheck",
327             "com.puppycrawl.tools.checkstyle.checks.coding.EqualsAvoidNullCheck",
328             "com.puppycrawl.tools.checkstyle.checks.coding.FinalLocalVariableCheck",
329             "com.puppycrawl.tools.checkstyle.checks.coding.HiddenFieldCheck",
330             "com.puppycrawl.tools.checkstyle.checks.coding.IllegalTypeCheck",
331             "com.puppycrawl.tools.checkstyle.checks.coding.MagicNumberCheck",
332             "com.puppycrawl.tools.checkstyle.checks.coding.MatchXpathCheck",
333             "com.puppycrawl.tools.checkstyle.checks.coding.ModifiedControlVariableCheck",
334             "com.puppycrawl.tools.checkstyle.checks.coding.NestedIfDepthCheck",
335             "com.puppycrawl.tools.checkstyle.checks.coding.OneStatementPerLineCheck",
336             "com.puppycrawl.tools.checkstyle.checks.coding.RequireThisCheck",
337             "com.puppycrawl.tools.checkstyle.checks.coding.UnusedLocalVariableCheck",
338             "com.puppycrawl.tools.checkstyle.checks.coding.VariableDeclarationUsageDistanceCheck",
339             "com.puppycrawl.tools.checkstyle.checks.design.HideUtilityClassConstructorCheck",
340             "com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck",
341             "com.puppycrawl.tools.checkstyle.checks.imports.ImportControlCheck",
342             "com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck",
343             "com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocContentLocationCheck",
344             "com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocMethodCheck",
345             "com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocPackageCheck",
346             "com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocParagraphCheck",
347             "com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocStyleCheck",
348             "com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTypeCheck",
349             "com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocVariableCheck",
350             "com.puppycrawl.tools.checkstyle.checks.javadoc.MissingJavadocMethodCheck",
351             "com.puppycrawl.tools.checkstyle.checks.javadoc.MissingJavadocPackageCheck",
352             "com.puppycrawl.tools.checkstyle.checks.javadoc.MissingJavadocTypeCheck",
353             "com.puppycrawl.tools.checkstyle.checks.javadoc.SummaryJavadocCheck",
354             "com.puppycrawl.tools.checkstyle.checks.javadoc.WriteTagCheck",
355             "com.puppycrawl.tools.checkstyle.checks.metrics.BooleanExpressionComplexityCheck",
356             "com.puppycrawl.tools.checkstyle.checks.metrics.ClassFanOutComplexityCheck",
357             "com.puppycrawl.tools.checkstyle.checks.metrics.CyclomaticComplexityCheck",
358             "com.puppycrawl.tools.checkstyle.checks.modifier.RedundantModifierCheck",
359             "com.puppycrawl.tools.checkstyle.checks.naming.AbbreviationAsWordInNameCheck",
360             "com.puppycrawl.tools.checkstyle.checks.naming.ConstantNameCheck",
361             "com.puppycrawl.tools.checkstyle.checks.naming.LocalFinalVariableNameCheck",
362             "com.puppycrawl.tools.checkstyle.checks.naming.LocalVariableNameCheck",
363             "com.puppycrawl.tools.checkstyle.checks.naming.MemberNameCheck",
364             "com.puppycrawl.tools.checkstyle.checks.naming.MethodNameCheck",
365             "com.puppycrawl.tools.checkstyle.checks.naming.ParameterNameCheck",
366             "com.puppycrawl.tools.checkstyle.checks.regexp.RegexpCheck",
367             "com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineCheck",
368             "com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck",
369             "com.puppycrawl.tools.checkstyle.checks.sizes.FileLengthCheck",
370             "com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck",
371             "com.puppycrawl.tools.checkstyle.checks.sizes.ParameterNumberCheck",
372             "com.puppycrawl.tools.checkstyle.checks.whitespace.MethodParamPadCheck",
373             "com.puppycrawl.tools.checkstyle.checks.whitespace.NoWhitespaceAfterCheck",
374             "com.puppycrawl.tools.checkstyle.checks.whitespace.ParenPadCheck",
375             "com.puppycrawl.tools.checkstyle.checks.whitespace.WhitespaceAfterCheck",
376             "com.puppycrawl.tools.checkstyle.checks.whitespace.WhitespaceAroundCheck",
377             "com.puppycrawl.tools.checkstyle.checks.SuppressWarningsHolder",
378             "com.puppycrawl.tools.checkstyle.filters.SuppressWithPlainTextCommentFilter",
379             "com.puppycrawl.tools.checkstyle.filters.SuppressionCommentFilter",
380             "com.puppycrawl.tools.checkstyle.filters.SuppressionXpathFilter",
381             "com.puppycrawl.tools.checkstyle.filters.SuppressionXpathSingleFilter"
382     );
383 
384     // This is a hack until https://github.com/checkstyle/checkstyle/issues/13845
385     private static final Map<String, String> MODULE_MAPPINGS = new HashMap<>();
386 
387     private static final Map<String, ModuleDetails> PUBLIC_MODULE_DETAILS_MAP = new HashMap<>();
388 
389     // -@cs[ExecutableStatementCount] Suppressing due to large module mappings
390     static {
391         MODULE_MAPPINGS.put("IllegalCatch",
392                 "com.puppycrawl.tools.checkstyle.checks.coding.IllegalCatchCheck");
393         MODULE_MAPPINGS.put("MagicNumber",
394                 "com.puppycrawl.tools.checkstyle.checks.coding.MagicNumberCheck");
395         MODULE_MAPPINGS.put("SummaryJavadoc",
396                 "com.puppycrawl.tools.checkstyle.checks.javadoc.SummaryJavadocCheck");
397         MODULE_MAPPINGS.put("ClassDataAbstractionCoupling",
398                 "com.puppycrawl.tools.checkstyle.checks.metrics.ClassDataAbstractionCouplingCheck");
399         MODULE_MAPPINGS.put("ConstantName",
400                 "com.puppycrawl.tools.checkstyle.checks.naming.ConstantNameCheck");
401         MODULE_MAPPINGS.put("MemberName",
402                 "com.puppycrawl.tools.checkstyle.checks.naming.MemberNameCheck");
403         MODULE_MAPPINGS.put("MethodName",
404                 "com.puppycrawl.tools.checkstyle.checks.naming.MethodNameCheck");
405         MODULE_MAPPINGS.put("ParameterName",
406                 "com.puppycrawl.tools.checkstyle.checks.naming.ParameterNameCheck");
407         MODULE_MAPPINGS.put("RegexpOnFilename",
408                 "com.puppycrawl.tools.checkstyle.checks.regexp.RegexpOnFilenameCheck");
409         MODULE_MAPPINGS.put("RegexpSingleline",
410                 "com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineCheck");
411         MODULE_MAPPINGS.put("RegexpSinglelineJava",
412                 "com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck");
413         MODULE_MAPPINGS.put("LineLength",
414                 "com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck");
415         MODULE_MAPPINGS.put("ParameterNumber",
416                 "com.puppycrawl.tools.checkstyle.checks.sizes.ParameterNumberCheck");
417         MODULE_MAPPINGS.put("NoWhitespaceAfter",
418                 "com.puppycrawl.tools.checkstyle.checks.whitespace.NoWhitespaceAfterCheck");
419         MODULE_MAPPINGS.put("OrderedProperties",
420                 "com.puppycrawl.tools.checkstyle.checks.OrderedPropertiesCheck");
421         MODULE_MAPPINGS.put("SuppressWarningsHolder",
422                 "com.puppycrawl.tools.checkstyle.checks.SuppressWarningsHolder");
423         MODULE_MAPPINGS.put("UniqueProperties",
424                 "com.puppycrawl.tools.checkstyle.checks.UniquePropertiesCheck");
425         MODULE_MAPPINGS.put("SuppressionXpathSingleFilter",
426                 "com.puppycrawl.tools.checkstyle.filters.SuppressionXpathSingleFilter");
427         MODULE_MAPPINGS.put("SuppressWarningsFilter",
428                 "com.puppycrawl.tools.checkstyle.filters.SuppressWarningsFilter");
429         MODULE_MAPPINGS.put("LeftCurly",
430                 "com.puppycrawl.tools.checkstyle.checks.blocks.LeftCurlyCheck");
431         MODULE_MAPPINGS.put("RequireThis",
432                 "com.puppycrawl.tools.checkstyle.checks.coding.RequireThisCheck");
433         MODULE_MAPPINGS.put("IllegalThrows",
434                 "com.puppycrawl.tools.checkstyle.checks.coding.IllegalThrowsCheck");
435         MODULE_MAPPINGS.put("LocalFinalVariableName",
436                 "com.puppycrawl.tools.checkstyle.checks.naming.LocalFinalVariableNameCheck");
437         MODULE_MAPPINGS.put("PackageName",
438                 "com.puppycrawl.tools.checkstyle.checks.naming.PackageNameCheck");
439         MODULE_MAPPINGS.put("RedundantModifier",
440                 "com.puppycrawl.tools.checkstyle.checks.modifier.RedundantModifierCheck");
441         MODULE_MAPPINGS.put("AbstractClassName",
442                 "com.puppycrawl.tools.checkstyle.checks.naming.AbstractClassNameCheck");
443         MODULE_MAPPINGS.put("JavadocMethod",
444                 "com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocMethodCheck");
445         MODULE_MAPPINGS.put("IllegalIdentifierName",
446                 "com.puppycrawl.tools.checkstyle.checks.naming.IllegalIdentifierNameCheck");
447         MODULE_MAPPINGS.put("FileLength",
448                 "com.puppycrawl.tools.checkstyle.checks.sizes.FileLengthCheck");
449         MODULE_MAPPINGS.put("EqualsAvoidNull",
450                 "com.puppycrawl.tools.checkstyle.checks.coding.EqualsAvoidNullCheck");
451         MODULE_MAPPINGS.put("JavadocStyle",
452                 "com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocStyleCheck");
453     }
454 
455     /** Stop instances being created. **/
456     private InlineConfigParser() {
457     }
458 
459     public static TestInputConfiguration parse(String inputFilePath) throws Exception {
460         return parse(inputFilePath, false);
461     }
462 
463     /**
464      * Parses the input file provided.
465      *
466      * @param inputFilePath the input file path.
467      * @param setFilteredViolations flag to set filtered violations.
468      * @throws Exception if unable to read file or file not formatted properly.
469      */
470     private static TestInputConfiguration parse(String inputFilePath,
471                                                 boolean setFilteredViolations) throws Exception {
472         final TestInputConfiguration.Builder testInputConfigBuilder =
473                 new TestInputConfiguration.Builder();
474         final Path filePath = Path.of(inputFilePath);
475         final List<String> lines = readFile(filePath);
476         try {
477             setModules(testInputConfigBuilder, inputFilePath, lines);
478         }
479         catch (Exception exc) {
480             throw new CheckstyleException("Config comment not specified properly in "
481                     + inputFilePath, exc);
482         }
483         try {
484             setViolations(testInputConfigBuilder, lines, setFilteredViolations);
485         }
486         catch (CheckstyleException exc) {
487             throw new CheckstyleException(exc.getMessage() + " in " + inputFilePath, exc);
488         }
489         return testInputConfigBuilder.build();
490     }
491 
492     public static List<TestInputViolation> getViolationsFromInputFile(String inputFilePath)
493             throws Exception {
494         final TestInputConfiguration.Builder testInputConfigBuilder =
495                 new TestInputConfiguration.Builder();
496         final Path filePath = Path.of(inputFilePath);
497         final List<String> lines = readFile(filePath);
498 
499         try {
500             for (int lineNo = 0; lineNo < lines.size(); lineNo++) {
501                 setViolations(testInputConfigBuilder, lines, false, lineNo, true);
502             }
503         }
504         catch (CheckstyleException exc) {
505             throw new CheckstyleException(exc.getMessage() + " in " + inputFilePath, exc);
506         }
507 
508         return testInputConfigBuilder.build().getViolations();
509     }
510 
511     public static TestInputConfiguration parseWithFilteredViolations(String inputFilePath)
512             throws Exception {
513         return parse(inputFilePath, true);
514     }
515 
516     /**
517      * Parse the input file with configuration in xml header.
518      *
519      * @param inputFilePath the input file path.
520      * @throws Exception if unable to parse the xml header
521      */
522     public static TestInputConfiguration parseWithXmlHeader(String inputFilePath)
523             throws Exception {
524 
525         final Path filePath = Path.of(inputFilePath);
526         final List<String> lines = readFile(filePath);
527         if (!checkIsXmlConfig(lines)) {
528             throw new CheckstyleException("Config cannot be parsed as xml.");
529         }
530 
531         final List<String> inlineConfig = getInlineConfig(lines);
532         final String stringXmlConfig = LATEST_DTD + String.join("", inlineConfig);
533         final InputSource inputSource = new InputSource(new StringReader(stringXmlConfig));
534         final Configuration xmlConfig = ConfigurationLoader.loadConfiguration(
535                 inputSource, new PropertiesExpander(System.getProperties()),
536                 ConfigurationLoader.IgnoredModulesOptions.EXECUTE
537         );
538         final String configName = xmlConfig.getName();
539         if (!"Checker".equals(configName)) {
540             throw new CheckstyleException(
541                     "First module should be Checker, but was " + configName);
542         }
543 
544         final TestInputConfiguration.Builder testInputConfigBuilder =
545                 new TestInputConfiguration.Builder();
546         testInputConfigBuilder.setXmlConfiguration(xmlConfig);
547         try {
548             setViolations(testInputConfigBuilder, lines, false);
549         }
550         catch (CheckstyleException exc) {
551             throw new CheckstyleException(exc.getMessage() + " in " + inputFilePath, exc);
552         }
553         return testInputConfigBuilder.buildWithXmlConfiguration();
554     }
555 
556     /**
557      * Check whether a file provides xml configuration.
558      *
559      * @param lines lines of the file
560      * @return true if a file provides xml configuration, otherwise false.
561      */
562     private static boolean checkIsXmlConfig(List<String> lines) {
563         return "/*xml".equals(lines.get(0));
564     }
565 
566     private static void setModules(TestInputConfiguration.Builder testInputConfigBuilder,
567                                    String inputFilePath, List<String> lines)
568             throws Exception {
569         if (!lines.get(0).startsWith("/*")) {
570             throw new CheckstyleException("Config not specified on top."
571                 + "Please see other inputs for examples of what is required.");
572         }
573 
574         final List<String> inlineConfig = getInlineConfig(lines);
575 
576         if (checkIsXmlConfig(lines)) {
577             final String stringXmlConfig = LATEST_DTD + String.join("", inlineConfig);
578             final InputSource inputSource = new InputSource(new StringReader(stringXmlConfig));
579             final Configuration xmlConfig = ConfigurationLoader.loadConfiguration(
580                 inputSource, new PropertiesExpander(System.getProperties()),
581                     ConfigurationLoader.IgnoredModulesOptions.EXECUTE
582             );
583             final String configName = xmlConfig.getName();
584             if (!"Checker".equals(configName)) {
585                 throw new CheckstyleException(
586                         "First module should be Checker, but was " + configName);
587             }
588             handleXmlConfig(testInputConfigBuilder, inputFilePath, xmlConfig.getChildren());
589         }
590         else {
591             handleKeyValueConfig(testInputConfigBuilder, inputFilePath, inlineConfig);
592         }
593     }
594 
595     private static List<String> getInlineConfig(List<String> lines) {
596         return lines.stream()
597                 .skip(1)
598                 .takeWhile(line -> !line.startsWith("*/"))
599                 .collect(Collectors.toUnmodifiableList());
600     }
601 
602     private static void handleXmlConfig(TestInputConfiguration.Builder testInputConfigBuilder,
603                                         String inputFilePath,
604                                         Configuration... modules)
605             throws CheckstyleException {
606 
607         for (Configuration module: modules) {
608             final String moduleName = module.getName();
609             if ("TreeWalker".equals(moduleName)) {
610                 handleXmlConfig(testInputConfigBuilder, inputFilePath, module.getChildren());
611             }
612             else {
613                 final ModuleInputConfiguration.Builder moduleInputConfigBuilder =
614                         new ModuleInputConfiguration.Builder();
615                 setModuleName(moduleInputConfigBuilder, inputFilePath, moduleName);
616                 setProperties(inputFilePath, module, moduleInputConfigBuilder);
617                 testInputConfigBuilder.addChildModule(moduleInputConfigBuilder.build());
618             }
619         }
620     }
621 
622     private static void handleKeyValueConfig(TestInputConfiguration.Builder testInputConfigBuilder,
623                                              String inputFilePath, List<String> lines)
624             throws CheckstyleException, IOException, ReflectiveOperationException {
625         int lineNo = 0;
626         while (lineNo < lines.size()) {
627             final ModuleInputConfiguration.Builder moduleInputConfigBuilder =
628                     new ModuleInputConfiguration.Builder();
629             final String moduleName = lines.get(lineNo);
630             setModuleName(moduleInputConfigBuilder, inputFilePath, moduleName);
631             setProperties(moduleInputConfigBuilder, inputFilePath, lines, lineNo + 1, moduleName);
632             testInputConfigBuilder.addChildModule(moduleInputConfigBuilder.build());
633             do {
634                 lineNo++;
635             } while (lineNo < lines.size()
636                     && lines.get(lineNo).isEmpty()
637                     || !lines.get(lineNo - 1).isEmpty());
638         }
639     }
640 
641     private static Map<String, String> getDefaultProperties(String fullyQualifiedClassName) {
642 
643         final Map<String, String> defaultProperties = new HashMap<>();
644         final boolean isSuppressedModule = SUPPRESSED_MODULES.contains(fullyQualifiedClassName);
645 
646         if (PUBLIC_MODULE_DETAILS_MAP.isEmpty()) {
647             XmlMetaReader.readAllModulesIncludingThirdPartyIfAny().forEach(module -> {
648                 PUBLIC_MODULE_DETAILS_MAP.put(module.getFullQualifiedName(), module);
649             });
650         }
651 
652         final ModuleDetails moduleDetails = PUBLIC_MODULE_DETAILS_MAP.get(fullyQualifiedClassName);
653 
654         if (!isSuppressedModule && moduleDetails != null) {
655             defaultProperties.putAll(moduleDetails.getProperties().stream()
656                     .filter(prop -> {
657                         return prop.getName() != null && prop.getDefaultValue() != null;
658                     })
659                     .collect(Collectors.toUnmodifiableMap(
660                             ModulePropertyDetails::getName,
661                             ModulePropertyDetails::getDefaultValue
662                     )));
663         }
664 
665         return defaultProperties;
666     }
667 
668     private static String getFullyQualifiedClassName(String filePath, String moduleName)
669             throws CheckstyleException {
670         String fullyQualifiedClassName;
671         if (MODULE_MAPPINGS.containsKey(moduleName)) {
672             fullyQualifiedClassName = MODULE_MAPPINGS.get(moduleName);
673         }
674         else if (moduleName.startsWith("com.")) {
675             fullyQualifiedClassName = moduleName;
676         }
677         else {
678             final String path = SLASH_PATTERN.matcher(filePath).replaceAll(".");
679             final int endIndex = path.lastIndexOf(moduleName.toLowerCase(Locale.ROOT));
680             if (endIndex == -1) {
681                 throw new CheckstyleException("Unable to resolve module name: " + moduleName
682                     + ". Please check for spelling errors or specify fully qualified class name.");
683             }
684             final int beginIndex = path.indexOf("com.puppycrawl");
685             fullyQualifiedClassName = path.substring(beginIndex, endIndex) + moduleName;
686             if (!fullyQualifiedClassName.endsWith("Filter")) {
687                 fullyQualifiedClassName += "Check";
688             }
689         }
690         return fullyQualifiedClassName;
691     }
692 
693     private static String getFilePath(String fileName, String inputFilePath) {
694         final int lastSlashIndex = Math.max(inputFilePath.lastIndexOf('\\'),
695                 inputFilePath.lastIndexOf('/'));
696         final String root = inputFilePath.substring(0, lastSlashIndex + 1);
697         return root + fileName;
698     }
699 
700     private static String getResourcePath(String fileName, String inputFilePath) {
701         final String filePath = getUriPath(fileName, inputFilePath);
702         final int lastSlashIndex = filePath.lastIndexOf('/');
703         final String root = filePath.substring(filePath.indexOf("puppycrawl") - 5,
704                 lastSlashIndex + 1);
705         return root + fileName;
706     }
707 
708     private static String getUriPath(String fileName, String inputFilePath) {
709         return new File(getFilePath(fileName, inputFilePath)).toURI().toString();
710     }
711 
712     private static String getResolvedPath(String fileValue, String inputFilePath) {
713         final String resolvedFilePath;
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 {
724             resolvedFilePath = getFilePath(fileValue, inputFilePath);
725         }
726         return resolvedFilePath;
727     }
728 
729     private static List<String> readFile(Path filePath) throws CheckstyleException {
730         try {
731             return Files.readAllLines(filePath);
732         }
733         catch (IOException exc) {
734             throw new CheckstyleException("Failed to read " + filePath, exc);
735         }
736     }
737 
738     private static void setModuleName(ModuleInputConfiguration.Builder moduleInputConfigBuilder,
739                                       String filePath, String moduleName)
740             throws CheckstyleException {
741         final String fullyQualifiedClassName = getFullyQualifiedClassName(filePath, moduleName);
742         moduleInputConfigBuilder.setModuleName(fullyQualifiedClassName);
743     }
744 
745     private static String toStringConvertForArrayValue(Object value) {
746         String result = NULL_STRING;
747 
748         if (value instanceof double[]) {
749             final double[] arr = (double[]) value;
750             result = Arrays.stream(arr)
751                            .boxed()
752                            .map(number -> {
753                                return BigDecimal.valueOf(number)
754                                                 .stripTrailingZeros()
755                                                 .toPlainString();
756                            })
757                            .collect(Collectors.joining(","));
758         }
759         else if (value instanceof int[]) {
760             result = Arrays.toString((int[]) value).replaceAll("[\\[\\]\\s]", "");
761         }
762         else if (value instanceof boolean[]) {
763             result = Arrays.toString((boolean[]) value).replaceAll("[\\[\\]\\s]", "");
764         }
765         else if (value instanceof long[]) {
766             result = Arrays.toString((long[]) value).replaceAll("[\\[\\]\\s]", "");
767         }
768         else if (value instanceof Object[]) {
769             result = Arrays.toString((Object[]) value).replaceAll("[\\[\\]\\s]", "");
770         }
771         return result;
772     }
773 
774     /**
775      * Validate default value.
776      *
777      * @param propertyName the property name.
778      * @param propertyDefaultValue the specified default value in the file.
779      * @param fullyQualifiedModuleName the fully qualified module name.
780      */
781     private static void validateDefault(String propertyName,
782                                            String propertyDefaultValue,
783                                            String fullyQualifiedModuleName)
784             throws ReflectiveOperationException {
785         final Object checkInstance = createCheckInstance(fullyQualifiedModuleName);
786         final Object actualDefault;
787         final Class<?> propertyType;
788         final String actualDefaultAsString;
789 
790         if ("tokens".equals(propertyName)) {
791             final Method getter = checkInstance.getClass().getMethod("getDefaultTokens");
792             actualDefault = getter.invoke(checkInstance);
793             propertyType = actualDefault.getClass();
794             final int[] arr = (int[]) actualDefault;
795             actualDefaultAsString = Arrays.stream(arr)
796                                           .mapToObj(TokenUtil::getTokenName)
797                                           .collect(Collectors.joining(", "));
798         }
799         else if ("javadocTokens".equals(propertyName)) {
800             final Method getter = checkInstance.getClass().getMethod("getDefaultJavadocTokens");
801             actualDefault = getter.invoke(checkInstance);
802             propertyType = actualDefault.getClass();
803             final int[] arr = (int[]) actualDefault;
804             actualDefaultAsString = Arrays.stream(arr)
805                                           .mapToObj(JavadocUtil::getTokenName)
806                                           .collect(Collectors.joining(", "));
807         }
808         else {
809             actualDefault = getPropertyDefaultValue(checkInstance, propertyName);
810             if (actualDefault == null) {
811                 propertyType = null;
812             }
813             else {
814                 propertyType = actualDefault.getClass();
815             }
816             actualDefaultAsString = convertDefaultValueToString(actualDefault);
817         }
818         if (!isDefaultValue(propertyDefaultValue, actualDefaultAsString, propertyType)) {
819             final String message = String.format(Locale.ROOT,
820                     "Default value mismatch for %s in %s: specified '%s' but actually is '%s'",
821                     propertyName, fullyQualifiedModuleName,
822                     propertyDefaultValue, actualDefaultAsString);
823             throw new IllegalArgumentException(message);
824         }
825     }
826 
827     private static boolean isCollectionValues(String specifiedDefault, String actualDefault) {
828         final Set<String> specifiedSet = new HashSet<>(
829             Arrays.asList(specifiedDefault.replaceAll("[\\[\\]\\s]", "").split(",")));
830         final Set<String> actualSet = new HashSet<>(
831             Arrays.asList(actualDefault.replaceAll("[\\[\\]\\s]", "").split(",")));
832         return actualSet.containsAll(specifiedSet);
833     }
834 
835     private static String convertDefaultValueToString(Object value) {
836         final String defaultValueAsString;
837         if (value == null) {
838             defaultValueAsString = NULL_STRING;
839         }
840         else if (value instanceof String) {
841             defaultValueAsString = toStringForStringValue((String) value);
842         }
843         else if (value.getClass().isArray()) {
844             defaultValueAsString = toStringConvertForArrayValue(value);
845         }
846         else if (value instanceof BitSet) {
847             defaultValueAsString = toStringForBitSetValue((BitSet) value);
848         }
849         else if (value instanceof Collection<?>) {
850             defaultValueAsString = toStringForCollectionValue((Collection<?>) value);
851         }
852         else {
853             defaultValueAsString = String.valueOf(value);
854         }
855         return defaultValueAsString;
856     }
857 
858     private static String toStringForStringValue(String strValue) {
859         final String str;
860         if (strValue.startsWith("(") && strValue.endsWith(")")) {
861             str = strValue.substring(1, strValue.length() - 1);
862         }
863         else {
864             str = strValue;
865         }
866         return str;
867     }
868 
869     private static String toStringForBitSetValue(BitSet bitSet) {
870         return bitSet.stream()
871                      .mapToObj(TokenUtil::getTokenName)
872                      .collect(Collectors.joining(","));
873     }
874 
875     private static String toStringForCollectionValue(Collection<?> collection) {
876         return collection.toString().replaceAll("[\\[\\]\\s]", "");
877     }
878 
879     /**
880      * Validate default value.
881      *
882      * @param propertyDefaultValue the specified default value in the file.
883      * @param actualDefault the actual default value
884      * @param fieldType the data type of default value.
885      */
886     private static boolean isDefaultValue(final String propertyDefaultValue,
887                                           final String actualDefault,
888                                           final Class<?> fieldType) {
889         final boolean result;
890 
891         if (NULL_STRING.equals(actualDefault)) {
892             result = isNull(propertyDefaultValue);
893         }
894         else if (isNumericType(fieldType)) {
895             final BigDecimal specified = new BigDecimal(propertyDefaultValue);
896             final BigDecimal actual = new BigDecimal(actualDefault);
897             result = specified.compareTo(actual) == 0;
898         }
899         else if (fieldType.isArray()
900             || Collection.class.isAssignableFrom(fieldType)
901             || BitSet.class.isAssignableFrom(fieldType)) {
902             result = isCollectionValues(propertyDefaultValue, actualDefault);
903         }
904         else if (fieldType.isEnum() || fieldType.isLocalClass()) {
905             result = propertyDefaultValue.equalsIgnoreCase(actualDefault);
906         }
907         else {
908             result = propertyDefaultValue.equals(actualDefault);
909         }
910         return result;
911     }
912 
913     private static Object createCheckInstance(String className) throws
914             ReflectiveOperationException {
915         final Class<?> checkClass = Class.forName(className);
916         return checkClass.getDeclaredConstructor().newInstance();
917     }
918 
919     private static String readPropertiesContent(int beginLineNo, List<String> lines) {
920         final StringBuilder stringBuilder = new StringBuilder(128);
921         int lineNo = beginLineNo;
922         String line = lines.get(lineNo);
923         while (!line.isEmpty() && !"*/".equals(line)) {
924             stringBuilder.append(line).append('\n');
925             lineNo++;
926             line = lines.get(lineNo);
927         }
928         return stringBuilder.toString();
929     }
930 
931     private static void validateProperties(Map<String, String> propertiesWithMissingDefaultTag,
932             List<String> unusedProperties) throws CheckstyleException {
933 
934         if (!propertiesWithMissingDefaultTag.isEmpty()) {
935 
936             final String propertiesList = propertiesWithMissingDefaultTag.entrySet().stream()
937                     .map(entry -> {
938                         return String.format(Locale.ROOT, "%s = (default)%s",
939                                 entry.getKey(), entry.getValue());
940                     })
941                     .collect(Collectors.joining(", "));
942 
943             final String message = String.format(Locale.ROOT,
944                     "Default properties must use the '(default)' tag."
945                     + " Properties missing the '(default)' tag: %s", propertiesList);
946             throw new CheckstyleException(message);
947         }
948         if (!unusedProperties.isEmpty()) {
949             final String message = String.format(Locale.ROOT,
950                     "All properties must be explicitly specified."
951                     + " Found unused properties: %s", unusedProperties);
952             throw new CheckstyleException(message);
953         }
954     }
955 
956     private static void validateDefaultProperties(
957         Map<Object, Object> actualProperties,
958         Map<String, String> defaultProperties) throws CheckstyleException {
959 
960         final Map<String, String> matchedProperties = actualProperties.entrySet().stream()
961                 .filter(entry -> {
962                     return entry.getValue()
963                         .equals(defaultProperties.get(entry.getKey().toString()));
964                 })
965                 .collect(HashMap::new,
966                         (map, entry) -> {
967                         map.put(entry.getKey().toString(), entry.getValue().toString());
968                     }, HashMap::putAll);
969         final List<String> missingProperties = defaultProperties.keySet().stream()
970                 .filter(propertyName -> !actualProperties.containsKey(propertyName))
971                 .collect(Collectors.toUnmodifiableList());
972 
973         validateProperties(matchedProperties, missingProperties);
974     }
975 
976     private static void setProperties(ModuleInputConfiguration.Builder inputConfigBuilder,
977                             String inputFilePath,
978                             List<String> lines,
979                             int beginLineNo, String moduleName)
980             throws IOException, CheckstyleException, ReflectiveOperationException {
981 
982         final String propertyContent = readPropertiesContent(beginLineNo, lines);
983         final Map<Object, Object> properties = loadProperties(propertyContent);
984         final String fullyQualifiedClassName =
985                 getFullyQualifiedClassName(inputFilePath, moduleName);
986 
987         validateDefaultProperties(properties, getDefaultProperties(fullyQualifiedClassName));
988 
989         for (final Map.Entry<Object, Object> entry : properties.entrySet()) {
990             final String key = entry.getKey().toString();
991             final String value = entry.getValue().toString();
992 
993             if (key.startsWith("message.")) {
994                 inputConfigBuilder.addModuleMessage(key.substring(8), value);
995             }
996             else if (NULL_STRING.equals(value)) {
997                 inputConfigBuilder.addNonDefaultProperty(key, null);
998             }
999             else if (value.startsWith("(file)")) {
1000                 final String fileName = value.substring(value.indexOf(')') + 1);
1001                 final String filePath = getResolvedPath(fileName, inputFilePath);
1002                 inputConfigBuilder.addNonDefaultProperty(key, filePath);
1003             }
1004             else if (value.startsWith("(default)")) {
1005                 final String defaultValue = value.substring(value.indexOf(')') + 1);
1006                 validateDefault(key, defaultValue, fullyQualifiedClassName);
1007 
1008                 if (NULL_STRING.equals(defaultValue)) {
1009                     inputConfigBuilder.addDefaultProperty(key, null);
1010                 }
1011                 else {
1012                     inputConfigBuilder.addDefaultProperty(key, defaultValue);
1013                 }
1014             }
1015             else {
1016                 inputConfigBuilder.addNonDefaultProperty(key, value);
1017             }
1018         }
1019     }
1020 
1021     private static void setProperties(String inputFilePath, Configuration module,
1022                                       ModuleInputConfiguration.Builder moduleInputConfigBuilder)
1023             throws CheckstyleException {
1024         final String[] getPropertyNames = module.getPropertyNames();
1025         for (final String propertyName : getPropertyNames) {
1026             final String propertyValue = module.getProperty(propertyName);
1027 
1028             if ("file".equals(propertyName)) {
1029                 final String filePath = getResolvedPath(propertyValue, inputFilePath);
1030                 moduleInputConfigBuilder.addNonDefaultProperty(propertyName, filePath);
1031             }
1032             else {
1033                 if (NULL_STRING.equals(propertyValue)) {
1034                     moduleInputConfigBuilder.addNonDefaultProperty(propertyName, null);
1035                 }
1036                 else {
1037                     moduleInputConfigBuilder.addNonDefaultProperty(propertyName, propertyValue);
1038                 }
1039             }
1040         }
1041 
1042         final Map<String, String> messages = module.getMessages();
1043         for (final Map.Entry<String, String> entry : messages.entrySet()) {
1044             final String key = entry.getKey();
1045             final String value = entry.getValue();
1046             moduleInputConfigBuilder.addModuleMessage(key, value);
1047         }
1048     }
1049 
1050     private static void setViolations(TestInputConfiguration.Builder inputConfigBuilder,
1051                                       List<String> lines, boolean useFilteredViolations)
1052             throws CheckstyleException {
1053         final List<ModuleInputConfiguration> moduleLists = inputConfigBuilder.getChildrenModules();
1054         final boolean specifyViolationMessage = moduleLists.size() == 1
1055                 && !PERMANENT_SUPPRESSED_CHECKS.contains(moduleLists.get(0).getModuleName())
1056                 && !SUPPRESSED_CHECKS.contains(moduleLists.get(0).getModuleName());
1057         for (int lineNo = 0; lineNo < lines.size(); lineNo++) {
1058             setViolations(inputConfigBuilder, lines,
1059                     useFilteredViolations, lineNo, specifyViolationMessage);
1060         }
1061     }
1062 
1063     /**
1064      * Sets the violations.
1065      *
1066      * @param inputConfigBuilder the input file path.
1067      * @param lines all the lines in the file.
1068      * @param useFilteredViolations flag to set filtered violations.
1069      * @param lineNo current line.
1070      * @noinspection IfStatementWithTooManyBranches
1071      * @noinspectionreason IfStatementWithTooManyBranches - complex logic of violation
1072      *      parser requires giant if/else
1073      * @throws CheckstyleException if violation message is not specified
1074      */
1075     // -@cs[ExecutableStatementCount] splitting this method is not reasonable.
1076     // -@cs[JavaNCSS] splitting this method is not reasonable.
1077     // -@cs[CyclomaticComplexity] splitting this method is not reasonable.
1078     private static void setViolations(TestInputConfiguration.Builder inputConfigBuilder,
1079                                       List<String> lines, boolean useFilteredViolations,
1080                                       int lineNo, boolean specifyViolationMessage)
1081             throws CheckstyleException {
1082         final String line = lines.get(lineNo);
1083         if (ANY_OK_VIOLATION_PATTERN.matcher(line).matches()
1084                 && !ALLOWED_OK_VIOLATION_PATTERN.matcher(line).matches()) {
1085             throw new CheckstyleException(
1086                     "Invalid format (must be \"// ok...\" or \"// violation...\"): " + line);
1087         }
1088 
1089         final Matcher violationMatcher =
1090                 VIOLATION_PATTERN.matcher(lines.get(lineNo));
1091         final Matcher violationAboveMatcher =
1092                 VIOLATION_ABOVE_PATTERN.matcher(lines.get(lineNo));
1093         final Matcher violationBelowMatcher =
1094                 VIOLATION_BELOW_PATTERN.matcher(lines.get(lineNo));
1095         final Matcher violationAboveWithExplanationMatcher =
1096                 VIOLATION_ABOVE_WITH_EXPLANATION_PATTERN.matcher(lines.get(lineNo));
1097         final Matcher violationBelowWithExplanationMatcher =
1098                 VIOLATION_BELOW_WITH_EXPLANATION_PATTERN.matcher(lines.get(lineNo));
1099         final Matcher violationWithExplanationMatcher =
1100                 VIOLATION_WITH_EXPLANATION_PATTERN.matcher(lines.get(lineNo));
1101         final Matcher multipleViolationsMatcher =
1102                 MULTIPLE_VIOLATIONS_PATTERN.matcher(lines.get(lineNo));
1103         final Matcher multipleViolationsAboveMatcher =
1104                 MULTIPLE_VIOLATIONS_ABOVE_PATTERN.matcher(lines.get(lineNo));
1105         final Matcher multipleViolationsBelowMatcher =
1106                 MULTIPLE_VIOLATIONS_BELOW_PATTERN.matcher(lines.get(lineNo));
1107         final Matcher violationSomeLinesAboveMatcher =
1108                 VIOLATION_SOME_LINES_ABOVE_PATTERN.matcher(lines.get(lineNo));
1109         final Matcher violationSomeLinesBelowMatcher =
1110                 VIOLATION_SOME_LINES_BELOW_PATTERN.matcher(lines.get(lineNo));
1111         final Matcher violationsAboveMatcherWithMessages =
1112                 VIOLATIONS_ABOVE_PATTERN_WITH_MESSAGES.matcher(lines.get(lineNo));
1113         final Matcher violationsSomeLinesAboveMatcher =
1114                 VIOLATIONS_SOME_LINES_ABOVE_PATTERN.matcher(lines.get(lineNo));
1115         final Matcher violationsSomeLinesBelowMatcher =
1116                 VIOLATIONS_SOME_LINES_BELOW_PATTERN.matcher(lines.get(lineNo));
1117         final Matcher violationsDefault =
1118                 VIOLATION_DEFAULT.matcher(lines.get(lineNo));
1119         if (violationMatcher.matches()) {
1120             final String violationMessage = violationMatcher.group(1);
1121             final int violationLineNum = lineNo + 1;
1122             checkWhetherViolationSpecified(specifyViolationMessage, violationMessage,
1123                     violationLineNum);
1124             inputConfigBuilder.addViolation(violationLineNum, violationMessage);
1125         }
1126         else if (violationAboveMatcher.matches()) {
1127             final String violationMessage = violationAboveMatcher.group(1);
1128             checkWhetherViolationSpecified(specifyViolationMessage, violationMessage, lineNo);
1129             inputConfigBuilder.addViolation(lineNo, violationMessage);
1130         }
1131         else if (violationBelowMatcher.matches()) {
1132             final String violationMessage = violationBelowMatcher.group(1);
1133             final int violationLineNum = lineNo + 2;
1134             checkWhetherViolationSpecified(specifyViolationMessage, violationMessage,
1135                     violationLineNum);
1136             inputConfigBuilder.addViolation(violationLineNum, violationMessage);
1137         }
1138         else if (violationAboveWithExplanationMatcher.matches()) {
1139             final String violationMessage = violationAboveWithExplanationMatcher.group(1);
1140             checkWhetherViolationSpecified(specifyViolationMessage, violationMessage, lineNo);
1141             inputConfigBuilder.addViolation(lineNo, violationMessage);
1142         }
1143         else if (violationBelowWithExplanationMatcher.matches()) {
1144             final String violationMessage = violationBelowWithExplanationMatcher.group(1);
1145             final int violationLineNum = lineNo + 2;
1146             checkWhetherViolationSpecified(specifyViolationMessage, violationMessage,
1147                     violationLineNum);
1148             inputConfigBuilder.addViolation(violationLineNum, violationMessage);
1149         }
1150         else if (violationWithExplanationMatcher.matches()) {
1151             final int violationLineNum = lineNo + 1;
1152             inputConfigBuilder.addViolation(violationLineNum, null);
1153         }
1154         else if (violationSomeLinesAboveMatcher.matches()) {
1155             final String violationMessage = violationSomeLinesAboveMatcher.group(2);
1156             final int linesAbove = Integer.parseInt(violationSomeLinesAboveMatcher.group(1)) - 1;
1157             final int violationLineNum = lineNo - linesAbove;
1158             checkWhetherViolationSpecified(specifyViolationMessage, violationMessage,
1159                     violationLineNum);
1160             inputConfigBuilder.addViolation(violationLineNum, violationMessage);
1161         }
1162         else if (violationSomeLinesBelowMatcher.matches()) {
1163             final String violationMessage = violationSomeLinesBelowMatcher.group(2);
1164             final int linesBelow = Integer.parseInt(violationSomeLinesBelowMatcher.group(1)) + 1;
1165             final int violationLineNum = lineNo + linesBelow;
1166             checkWhetherViolationSpecified(specifyViolationMessage, violationMessage,
1167                     violationLineNum);
1168             inputConfigBuilder.addViolation(violationLineNum, violationMessage);
1169         }
1170         else if (violationsAboveMatcherWithMessages.matches()) {
1171             inputConfigBuilder.addViolations(
1172                 getExpectedViolationsForSpecificLine(
1173                     lines, lineNo, lineNo, violationsAboveMatcherWithMessages));
1174         }
1175         else if (violationsSomeLinesAboveMatcher.matches()) {
1176             inputConfigBuilder.addViolations(
1177                 getExpectedViolations(
1178                     lines, lineNo, violationsSomeLinesAboveMatcher, true));
1179         }
1180         else if (violationsSomeLinesBelowMatcher.matches()) {
1181             inputConfigBuilder.addViolations(
1182                     getExpectedViolations(
1183                             lines, lineNo, violationsSomeLinesBelowMatcher, false));
1184         }
1185         else if (multipleViolationsMatcher.matches()) {
1186             Collections
1187                     .nCopies(Integer.parseInt(multipleViolationsMatcher.group(1)), lineNo + 1)
1188                     .forEach(actualLineNumber -> {
1189                         inputConfigBuilder.addViolation(actualLineNumber, null);
1190                     });
1191         }
1192         else if (multipleViolationsAboveMatcher.matches()) {
1193             Collections
1194                     .nCopies(Integer.parseInt(multipleViolationsAboveMatcher.group(1)), lineNo)
1195                     .forEach(actualLineNumber -> {
1196                         inputConfigBuilder.addViolation(actualLineNumber, null);
1197                     });
1198         }
1199         else if (multipleViolationsBelowMatcher.matches()) {
1200             Collections
1201                     .nCopies(Integer.parseInt(multipleViolationsBelowMatcher.group(1)),
1202                             lineNo + 2)
1203                     .forEach(actualLineNumber -> {
1204                         inputConfigBuilder.addViolation(actualLineNumber, null);
1205                     });
1206         }
1207         else if (useFilteredViolations) {
1208             setFilteredViolation(inputConfigBuilder, lineNo + 1,
1209                     lines.get(lineNo), specifyViolationMessage);
1210         }
1211         else if (violationsDefault.matches()) {
1212             final int violationLineNum = lineNo + 1;
1213             inputConfigBuilder.addViolation(violationLineNum, null);
1214         }
1215     }
1216 
1217     private static List<TestInputViolation> getExpectedViolationsForSpecificLine(
1218                                               List<String> lines, int lineNo, int violationLineNum,
1219                                               Matcher matcher) {
1220         final List<TestInputViolation> results = new ArrayList<>();
1221 
1222         final int expectedMessageCount =
1223             Integer.parseInt(matcher.group(1));
1224         for (int index = 1; index <= expectedMessageCount; index++) {
1225             final String lineWithMessage = lines.get(lineNo + index);
1226             final Matcher messageMatcher = VIOLATION_MESSAGE_PATTERN.matcher(lineWithMessage);
1227             if (messageMatcher.find()) {
1228                 final String violationMessage = messageMatcher.group(1);
1229                 results.add(new TestInputViolation(violationLineNum, violationMessage));
1230             }
1231         }
1232         if (results.size() != expectedMessageCount) {
1233             final String message = String.format(Locale.ROOT,
1234                 "Declared amount of violation messages at line %s is %s but found %s",
1235                 lineNo + 1, expectedMessageCount, results.size());
1236             throw new IllegalStateException(message);
1237         }
1238         return results;
1239     }
1240 
1241     private static List<TestInputViolation> getExpectedViolations(
1242                                               List<String> lines, int lineNo,
1243                                               Matcher matcher, boolean isAbove) {
1244         final int violationLine =
1245             Integer.parseInt(matcher.group(2));
1246         final int violationLineNum;
1247         if (isAbove) {
1248             violationLineNum = lineNo - violationLine + 1;
1249         }
1250         else {
1251             violationLineNum = lineNo + violationLine + 1;
1252         }
1253         return getExpectedViolationsForSpecificLine(lines,
1254             lineNo, violationLineNum, matcher);
1255     }
1256 
1257     private static void setFilteredViolation(TestInputConfiguration.Builder inputConfigBuilder,
1258                                              int lineNo, String line,
1259                                              boolean specifyViolationMessage)
1260             throws CheckstyleException {
1261         final Matcher violationMatcher =
1262                 FILTERED_VIOLATION_PATTERN.matcher(line);
1263         final Matcher violationAboveMatcher =
1264                 FILTERED_VIOLATION_ABOVE_PATTERN.matcher(line);
1265         final Matcher violationBelowMatcher =
1266                 FILTERED_VIOLATION_BELOW_PATTERN.matcher(line);
1267         final Matcher violationSomeLinesAboveMatcher =
1268                 FILTERED_VIOLATION_SOME_LINES_ABOVE_PATTERN.matcher(line);
1269         if (violationMatcher.matches()) {
1270             final String violationMessage = violationMatcher.group(1);
1271             checkWhetherViolationSpecified(specifyViolationMessage, violationMessage, lineNo);
1272             inputConfigBuilder.addFilteredViolation(lineNo, violationMessage);
1273         }
1274         else if (violationAboveMatcher.matches()) {
1275             final String violationMessage = violationAboveMatcher.group(1);
1276             final int violationLineNum = lineNo - 1;
1277             checkWhetherViolationSpecified(specifyViolationMessage, violationMessage,
1278                     violationLineNum);
1279             inputConfigBuilder.addFilteredViolation(violationLineNum, violationMessage);
1280         }
1281         else if (violationBelowMatcher.matches()) {
1282             final String violationMessage = violationBelowMatcher.group(1);
1283             final int violationLineNum = lineNo + 1;
1284             checkWhetherViolationSpecified(specifyViolationMessage, violationMessage,
1285                     violationLineNum);
1286             inputConfigBuilder.addFilteredViolation(violationLineNum, violationMessage);
1287         }
1288         else if (violationSomeLinesAboveMatcher.matches()) {
1289             final String violationMessage = violationSomeLinesAboveMatcher.group(2);
1290             final int linesAbove = Integer.parseInt(violationSomeLinesAboveMatcher.group(1)) - 1;
1291             final int violationLineNum = lineNo - linesAbove;
1292             checkWhetherViolationSpecified(specifyViolationMessage, violationMessage,
1293                     violationLineNum);
1294             inputConfigBuilder.addFilteredViolation(violationLineNum, violationMessage);
1295         }
1296     }
1297 
1298     /**
1299      * Check whether violation is specified along with {@code // violation} comment.
1300      *
1301      * @param shouldViolationMsgBeSpecified should violation messages be specified.
1302      * @param violationMessage violation message
1303      * @param lineNum line number
1304      * @throws CheckstyleException if violation message is not specified
1305      */
1306     private static void checkWhetherViolationSpecified(boolean shouldViolationMsgBeSpecified,
1307             String violationMessage, int lineNum) throws CheckstyleException {
1308         if (shouldViolationMsgBeSpecified && violationMessage == null) {
1309             throw new CheckstyleException(
1310                     "Violation message should be specified on line " + lineNum);
1311         }
1312     }
1313 
1314     private static Map<Object, Object> loadProperties(String propertyContent) throws IOException {
1315         final Properties properties = new Properties();
1316         properties.load(new StringReader(propertyContent));
1317         return properties;
1318     }
1319 
1320     private static boolean isNumericType(Class<?> fieldType) {
1321         return Number.class.isAssignableFrom(fieldType)
1322                 || fieldType.equals(int.class)
1323                 || fieldType.equals(double.class)
1324                 || fieldType.equals(long.class)
1325                 || fieldType.equals(float.class);
1326     }
1327 
1328     public static Object getPropertyDefaultValue(Object checkInstance,
1329                                                  String propertyName)
1330             throws IllegalAccessException {
1331         Object result = null;
1332         Class<?> currentClass = checkInstance.getClass();
1333         while (currentClass != null) {
1334             try {
1335                 final Field field = currentClass.getDeclaredField(propertyName);
1336                 field.setAccessible(true);
1337                 result = field.get(checkInstance);
1338                 break;
1339             }
1340             catch (NoSuchFieldException exc) {
1341                 currentClass = currentClass.getSuperclass();
1342             }
1343         }
1344         return result;
1345     }
1346 
1347     private static boolean isNull(String propertyDefaultValue) {
1348         return NULL_STRING.equals(propertyDefaultValue)
1349                 || propertyDefaultValue.isEmpty()
1350                 || "null".equals(propertyDefaultValue)
1351                 || "\"\"".equals(propertyDefaultValue);
1352     }
1353 
1354 }