View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2026 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.internal.utils;
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.lang.reflect.Field;
25  import java.nio.charset.Charset;
26  import java.text.MessageFormat;
27  import java.util.Arrays;
28  import java.util.HashSet;
29  import java.util.Locale;
30  import java.util.OptionalInt;
31  import java.util.Properties;
32  import java.util.Set;
33  import java.util.stream.Collectors;
34  
35  import javax.xml.parsers.DocumentBuilder;
36  import javax.xml.parsers.DocumentBuilderFactory;
37  
38  import org.w3c.dom.Document;
39  import org.w3c.dom.Element;
40  import org.w3c.dom.Node;
41  import org.w3c.dom.NodeList;
42  
43  import com.google.common.reflect.ClassPath;
44  import com.puppycrawl.tools.checkstyle.api.FileText;
45  import com.puppycrawl.tools.checkstyle.checks.coding.AbstractSuperCheck;
46  import com.puppycrawl.tools.checkstyle.checks.naming.AbstractAccessControlNameCheck;
47  import com.puppycrawl.tools.checkstyle.checks.naming.AbstractNameCheck;
48  import com.puppycrawl.tools.checkstyle.checks.regexp.MultilineDetector;
49  import com.puppycrawl.tools.checkstyle.checks.regexp.RegexpMultilineCheck;
50  import com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineCheck;
51  import com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck;
52  import com.puppycrawl.tools.checkstyle.checks.regexp.SinglelineDetector;
53  import com.puppycrawl.tools.checkstyle.checks.whitespace.AbstractParenPadCheck;
54  import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
55  import com.puppycrawl.tools.checkstyle.utils.ModuleReflectionUtil;
56  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
57  
58  public final class CheckUtil {
59  
60      public static final String CRLF = "\r\n";
61  
62      private CheckUtil() {
63      }
64  
65      public static Set<String> getConfigCheckStyleModules() {
66          return getCheckStyleModulesReferencedInConfig("config/checkstyle-checks.xml");
67      }
68  
69      public static Set<String> getConfigSunStyleModules() {
70          return getCheckStyleModulesReferencedInConfig("src/main/resources/sun_checks.xml");
71      }
72  
73      public static Set<String> getConfigGoogleStyleModules() {
74          return getCheckStyleModulesReferencedInConfig("src/main/resources/google_checks.xml");
75      }
76  
77      public static Set<String> getConfigOpenJdkStyleModules() {
78          return getCheckStyleModulesReferencedInConfig("src/main/resources/openjdk_checks.xml");
79      }
80  
81      public static Set<String> getConfigDocCommentsStyleModules() {
82          return getCheckStyleModulesReferencedInConfig("src/main/resources/doc_comments_checks.xml");
83      }
84  
85      /**
86       * Retrieves a set of class names, removing 'Check' from the end if the class is
87       * a checkstyle check.
88       *
89       * @param checks class instances.
90       * @return a set of simple names.
91       */
92      public static Set<String> getSimpleNames(Set<Class<?>> checks) {
93          return checks.stream().map(check -> {
94              String name = check.getSimpleName();
95  
96              if (name.endsWith("Check")) {
97                  name = name.substring(0, name.length() - 5);
98              }
99  
100             return name;
101         }).collect(Collectors.toCollection(HashSet::new));
102     }
103 
104     /**
105      * Gets a set of names of checkstyle's checks which are referenced in checkstyle-checks.xml.
106      *
107      * @param configFilePath
108      *            file path of checkstyle-checks.xml.
109      * @return names of checkstyle's checks which are referenced in checkstyle-checks.xml.
110      */
111     private static Set<String> getCheckStyleModulesReferencedInConfig(String configFilePath) {
112         try {
113             final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
114 
115             // Validations of XML file make parsing too slow, that is why we
116             // disable all validations.
117             factory.setNamespaceAware(false);
118             factory.setValidating(false);
119             factory.setFeature("http://xml.org/sax/features/namespaces", false);
120             factory.setFeature("http://xml.org/sax/features/validation", false);
121             factory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar",
122                     false);
123             factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd",
124                     false);
125 
126             final DocumentBuilder builder = factory.newDocumentBuilder();
127             final Document document = builder.parse(new File(configFilePath));
128 
129             // optional, but recommended
130             // FYI:
131             // http://stackoverflow.com/questions/13786607/normalization-in-dom-parsing-with-java-
132             // how-does-it-work
133             document.getDocumentElement().normalize();
134 
135             final NodeList nodeList = document.getElementsByTagName("module");
136 
137             final Set<String> checksReferencedInCheckstyleChecksXml = new HashSet<>();
138             for (int index = 0; index < nodeList.getLength(); index++) {
139                 final Node currentNode = nodeList.item(index);
140                 if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
141                     final Element module = (Element) currentNode;
142                     final String checkName = module.getAttribute("name");
143                     checksReferencedInCheckstyleChecksXml.add(checkName);
144                 }
145             }
146             return checksReferencedInCheckstyleChecksXml;
147         }
148         catch (Exception exception) {
149             throw new IllegalStateException(exception);
150         }
151     }
152 
153     /**
154      * Gets all checkstyle's non-abstract checks.
155      *
156      * @return the set of checkstyle's non-abstract check classes.
157      * @throws IOException if the attempt to read class path resources failed.
158      */
159     public static Set<Class<?>> getCheckstyleChecks() throws IOException {
160         final ClassLoader loader = Thread.currentThread()
161                 .getContextClassLoader();
162         final String packageName = "com.puppycrawl.tools.checkstyle";
163         return getCheckstyleModulesRecursive(packageName, loader).stream()
164                 .filter(ModuleReflectionUtil::isCheckstyleTreeWalkerCheck)
165                 .collect(Collectors.toUnmodifiableSet());
166     }
167 
168     /**
169      * Gets all checkstyle's modules.
170      *
171      * @return the set of checkstyle's module classes.
172      * @throws IOException if the attempt to read class path resources failed.
173      */
174     public static Set<Class<?>> getCheckstyleModules() throws IOException {
175         final ClassLoader loader = Thread.currentThread()
176                 .getContextClassLoader();
177         final String packageName = "com.puppycrawl.tools.checkstyle";
178         return getCheckstyleModulesRecursive(packageName, loader);
179     }
180 
181     /**
182      * Gets checkstyle's modules in the given package recursively.
183      *
184      * @param packageName the package name to use
185      * @param loader the class loader used to load Checkstyle package name
186      * @return the set of checkstyle's module classes
187      * @throws IOException if the attempt to read class path resources failed
188      * @see ModuleReflectionUtil#isCheckstyleModule(Class)
189      */
190     private static Set<Class<?>> getCheckstyleModulesRecursive(
191             String packageName, ClassLoader loader)
192                     throws IOException {
193         final ClassPath classPath = ClassPath.from(loader);
194         return classPath.getTopLevelClassesRecursive(packageName).stream()
195                 .map(ClassPath.ClassInfo::load)
196                 .filter(ModuleReflectionUtil::isCheckstyleModule)
197                 .filter(CheckUtil::isFromAllowedPackages)
198                 .collect(Collectors.toUnmodifiableSet());
199     }
200 
201     /**
202      * Checks that class is from allowed packages.
203      *
204      * @param cls class to check
205      * @return true if class is from allowed packages, false otherwise
206      */
207     private static boolean isFromAllowedPackages(Class<?> cls) {
208         final String canonicalName = cls.getCanonicalName();
209         return !canonicalName.startsWith("com.puppycrawl.tools.checkstyle.packageobjectfactory")
210             && !canonicalName.startsWith("com.puppycrawl.tools.checkstyle.internal.testmodules")
211             && !canonicalName.startsWith("com.puppycrawl.tools.checkstyle.site");
212     }
213 
214     /**
215      * Gets the check's messages without scanning the class hierarchy.
216      *
217      * @param module class to examine.
218      * @return a set of checkstyle's module message fields.
219      */
220     public static Set<Field> getCheckMessagesWithoutDeepScan(Class<?> module) {
221         return getCheckMessages(module, ScanMode.NO_DEEP_SCAN);
222     }
223 
224     /**
225      * Gets the check's messages, scanning the class hierarchy deeply.
226      *
227      * @param module class to examine.
228      * @return a set of checkstyle's module message fields.
229      */
230     public static Set<Field> getCheckMessagesWithDeepScan(Class<?> module) {
231         return getCheckMessages(module, ScanMode.DEEP_SCAN);
232     }
233 
234     /**
235      * Gets the check's messages.
236      *
237      * @param module class to examine.
238      * @param scanMode mode defining whether to scan subclasses.
239      * @return a set of checkstyle's module message fields.
240      */
241     private static Set<Field> getCheckMessages(Class<?> module, ScanMode scanMode) {
242         final Set<Field> checkstyleMessages = new HashSet<>();
243 
244         // get all fields from current class
245         final Field[] fields = module.getDeclaredFields();
246 
247         for (Field field : fields) {
248             if (field.getName().startsWith("MSG_")) {
249                 checkstyleMessages.add(field);
250             }
251         }
252 
253         // deep scan class through hierarchy
254         final Class<?> superModule = module.getSuperclass();
255 
256         if (superModule != null
257                 && (scanMode == ScanMode.DEEP_SCAN
258                     || shouldScanDeepClassForMessages(superModule))) {
259             checkstyleMessages.addAll(getCheckMessages(superModule, scanMode));
260         }
261 
262         // special cases that require additional classes
263         if (module == RegexpMultilineCheck.class) {
264             checkstyleMessages.addAll(getCheckMessages(MultilineDetector.class, scanMode));
265         }
266         else if (module == RegexpSinglelineCheck.class
267                 || module == RegexpSinglelineJavaCheck.class) {
268             checkstyleMessages.addAll(getCheckMessages(SinglelineDetector.class, scanMode));
269         }
270 
271         return checkstyleMessages;
272     }
273 
274     /**
275      * Should the class be deep scanned for messages.
276      *
277      * @param superModule The class to examine.
278      * @return {@code true} if the class should be deeply scanned.
279      */
280     private static boolean shouldScanDeepClassForMessages(Class<?> superModule) {
281         return superModule == AbstractNameCheck.class
282                 || superModule == AbstractAccessControlNameCheck.class
283                 || superModule == AbstractParenPadCheck.class
284                 || superModule == AbstractSuperCheck.class;
285     }
286 
287     /**
288      * Gets the check message 'as is' from appropriate 'messages.properties'
289      * file.
290      *
291      * @param module The package the message is located in.
292      * @param locale the locale to get the message for.
293      * @param messageKey the key of message in 'messages*.properties' file.
294      * @param arguments the arguments of message in 'messages*.properties' file.
295      * @return the check's formatted message.
296      */
297     public static String getCheckMessage(Class<?> module, Locale locale, String messageKey,
298             Object... arguments) {
299         String checkMessage;
300         try {
301             final Properties pr = new Properties();
302             if (locale.equals(Locale.ENGLISH)) {
303                 pr.load(module.getResourceAsStream("messages.properties"));
304             }
305             else {
306                 pr.load(module
307                         .getResourceAsStream("messages_" + locale.getLanguage() + ".properties"));
308             }
309             final MessageFormat formatter = new MessageFormat(pr.getProperty(messageKey), locale);
310             checkMessage = formatter.format(arguments);
311         }
312         catch (IOException ignored) {
313             checkMessage = null;
314         }
315         return checkMessage;
316     }
317 
318     public static String getTokenText(int[] tokens, int... subtractions) {
319         final String tokenText;
320         if (subtractions.length == 0 && Arrays.equals(tokens, TokenUtil.getAllTokenIds())) {
321             tokenText = "TokenTypes.";
322         }
323         else {
324             final StringBuilder result = new StringBuilder(50);
325             boolean first = true;
326 
327             for (int token : tokens) {
328                 boolean found = false;
329 
330                 for (int subtraction : subtractions) {
331                     if (subtraction == token) {
332                         found = true;
333                         break;
334                     }
335                 }
336 
337                 if (found) {
338                     continue;
339                 }
340 
341                 if (first) {
342                     first = false;
343                 }
344                 else {
345                     result.append(", ");
346                 }
347 
348                 result.append(TokenUtil.getTokenName(token));
349             }
350 
351             if (!result.isEmpty()) {
352                 result.append('.');
353             }
354 
355             tokenText = result.toString();
356         }
357         return tokenText;
358     }
359 
360     public static Set<String> getTokenNameSet(int... tokens) {
361         final Set<String> result = new HashSet<>();
362 
363         for (int token : tokens) {
364             result.add(TokenUtil.getTokenName(token));
365         }
366 
367         return result;
368     }
369 
370     public static String getJavadocTokenText(int[] tokens, int... subtractions) {
371         final StringBuilder result = new StringBuilder(50);
372         boolean first = true;
373 
374         for (int token : tokens) {
375             boolean found = false;
376 
377             for (int subtraction : subtractions) {
378                 if (subtraction == token) {
379                     found = true;
380                     break;
381                 }
382             }
383 
384             if (found) {
385                 continue;
386             }
387 
388             if (first) {
389                 first = false;
390             }
391             else {
392                 result.append(", ");
393             }
394 
395             result.append(JavadocUtil.getTokenName(token));
396         }
397 
398         if (!result.isEmpty()) {
399             result.append('.');
400         }
401 
402         return result.toString();
403     }
404 
405     public static String getLineSeparatorForFile(String filepath, Charset charset)
406             throws IOException {
407         final OptionalInt endOfLineChar = new FileText(new File(filepath), charset.name())
408                 .getFullText()
409                 .chars()
410                 .filter(character -> character == '\r' || character == '\n')
411                 .findFirst();
412 
413         final String result;
414         if (endOfLineChar.isPresent() && endOfLineChar.getAsInt() == '\r') {
415             result = CRLF;
416         }
417         else {
418             result = "\n";
419         }
420         return result;
421     }
422 
423     /**
424      * Defines whether a class hierarchy should be scanned deeply for messages.
425      */
426     private enum ScanMode {
427 
428         /** Scan the class hierarchy deeply. */
429         DEEP_SCAN,
430         /** Scan only the given class. */
431         NO_DEEP_SCAN
432 
433     }
434 
435 }