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.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.RegexpMultilineCheck;
49  import com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineCheck;
50  import com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck;
51  import com.puppycrawl.tools.checkstyle.checks.whitespace.AbstractParenPadCheck;
52  import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
53  import com.puppycrawl.tools.checkstyle.utils.ModuleReflectionUtil;
54  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
55  
56  public final class CheckUtil {
57  
58      public static final String CRLF = "\r\n";
59  
60      private CheckUtil() {
61      }
62  
63      public static Set<String> getConfigCheckStyleModules() {
64          return getCheckStyleModulesReferencedInConfig("config/checkstyle-checks.xml");
65      }
66  
67      public static Set<String> getConfigSunStyleModules() {
68          return getCheckStyleModulesReferencedInConfig("src/main/resources/sun_checks.xml");
69      }
70  
71      public static Set<String> getConfigGoogleStyleModules() {
72          return getCheckStyleModulesReferencedInConfig("src/main/resources/google_checks.xml");
73      }
74  
75      /**
76       * Retrieves a set of class names, removing 'Check' from the end if the class is
77       * a checkstyle check.
78       *
79       * @param checks class instances.
80       * @return a set of simple names.
81       */
82      public static Set<String> getSimpleNames(Set<Class<?>> checks) {
83          return checks.stream().map(check -> {
84              String name = check.getSimpleName();
85  
86              if (name.endsWith("Check")) {
87                  name = name.substring(0, name.length() - 5);
88              }
89  
90              return name;
91          }).collect(Collectors.toCollection(HashSet::new));
92      }
93  
94      /**
95       * Gets a set of names of checkstyle's checks which are referenced in checkstyle-checks.xml.
96       *
97       * @param configFilePath
98       *            file path of checkstyle-checks.xml.
99       * @return names of checkstyle's checks which are referenced in checkstyle-checks.xml.
100      */
101     private static Set<String> getCheckStyleModulesReferencedInConfig(String configFilePath) {
102         try {
103             final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
104 
105             // Validations of XML file make parsing too slow, that is why we
106             // disable all validations.
107             factory.setNamespaceAware(false);
108             factory.setValidating(false);
109             factory.setFeature("http://xml.org/sax/features/namespaces", false);
110             factory.setFeature("http://xml.org/sax/features/validation", false);
111             factory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar",
112                     false);
113             factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd",
114                     false);
115 
116             final DocumentBuilder builder = factory.newDocumentBuilder();
117             final Document document = builder.parse(new File(configFilePath));
118 
119             // optional, but recommended
120             // FYI:
121             // http://stackoverflow.com/questions/13786607/normalization-in-dom-parsing-with-java-
122             // how-does-it-work
123             document.getDocumentElement().normalize();
124 
125             final NodeList nodeList = document.getElementsByTagName("module");
126 
127             final Set<String> checksReferencedInCheckstyleChecksXml = new HashSet<>();
128             for (int i = 0; i < nodeList.getLength(); i++) {
129                 final Node currentNode = nodeList.item(i);
130                 if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
131                     final Element module = (Element) currentNode;
132                     final String checkName = module.getAttribute("name");
133                     checksReferencedInCheckstyleChecksXml.add(checkName);
134                 }
135             }
136             return checksReferencedInCheckstyleChecksXml;
137         }
138         catch (Exception exception) {
139             throw new IllegalStateException(exception);
140         }
141     }
142 
143     /**
144      * Gets all checkstyle's non-abstract checks.
145      *
146      * @return the set of checkstyle's non-abstract check classes.
147      * @throws IOException if the attempt to read class path resources failed.
148      */
149     public static Set<Class<?>> getCheckstyleChecks() throws IOException {
150         final ClassLoader loader = Thread.currentThread()
151                 .getContextClassLoader();
152         final String packageName = "com.puppycrawl.tools.checkstyle";
153         return getCheckstyleModulesRecursive(packageName, loader).stream()
154                 .filter(ModuleReflectionUtil::isCheckstyleTreeWalkerCheck)
155                 .collect(Collectors.toUnmodifiableSet());
156     }
157 
158     /**
159      * Gets all checkstyle's modules.
160      *
161      * @return the set of checkstyle's module classes.
162      * @throws IOException if the attempt to read class path resources failed.
163      */
164     public static Set<Class<?>> getCheckstyleModules() throws IOException {
165         final ClassLoader loader = Thread.currentThread()
166                 .getContextClassLoader();
167         final String packageName = "com.puppycrawl.tools.checkstyle";
168         return getCheckstyleModulesRecursive(packageName, loader);
169     }
170 
171     /**
172      * Gets checkstyle's modules in the given package recursively.
173      *
174      * @param packageName the package name to use
175      * @param loader the class loader used to load Checkstyle package name
176      * @return the set of checkstyle's module classes
177      * @throws IOException if the attempt to read class path resources failed
178      * @see ModuleReflectionUtil#isCheckstyleModule(Class)
179      */
180     private static Set<Class<?>> getCheckstyleModulesRecursive(
181             String packageName, ClassLoader loader) throws IOException {
182         final ClassPath classPath = ClassPath.from(loader);
183         return classPath.getTopLevelClassesRecursive(packageName).stream()
184                 .map(ClassPath.ClassInfo::load)
185                 .filter(ModuleReflectionUtil::isCheckstyleModule)
186                 .filter(CheckUtil::isFromAllowedPackages)
187                 .collect(Collectors.toUnmodifiableSet());
188     }
189 
190     /**
191      * Checks that class is from allowed packages.
192      *
193      * @param cls class to check
194      * @return true if class is from allowed packages, false otherwise
195      */
196     private static boolean isFromAllowedPackages(Class<?> cls) {
197         final String canonicalName = cls.getCanonicalName();
198         return !canonicalName.startsWith("com.puppycrawl.tools.checkstyle.packageobjectfactory")
199             && !canonicalName.startsWith("com.puppycrawl.tools.checkstyle.internal.testmodules")
200             && !canonicalName.startsWith("com.puppycrawl.tools.checkstyle.site");
201     }
202 
203     /**
204      * Gets the check's messages.
205      *
206      * @param module class to examine.
207      * @param deepScan scan subclasses.
208      * @return a set of checkstyle's module message fields.
209      * @throws ClassNotFoundException if the attempt to read a protected class fails.
210      * @noinspection BooleanParameter
211      * @noinspectionreason BooleanParameter - boolean parameter eases testing
212      */
213     public static Set<Field> getCheckMessages(Class<?> module, boolean deepScan)
214             throws ClassNotFoundException {
215         final Set<Field> checkstyleMessages = new HashSet<>();
216 
217         // get all fields from current class
218         final Field[] fields = module.getDeclaredFields();
219 
220         for (Field field : fields) {
221             if (field.getName().startsWith("MSG_")) {
222                 checkstyleMessages.add(field);
223             }
224         }
225 
226         // deep scan class through hierarchy
227         final Class<?> superModule = module.getSuperclass();
228 
229         if (superModule != null && (deepScan || shouldScanDeepClassForMessages(superModule))) {
230             checkstyleMessages.addAll(getCheckMessages(superModule, deepScan));
231         }
232 
233         // special cases that require additional classes
234         if (module == RegexpMultilineCheck.class) {
235             checkstyleMessages.addAll(getCheckMessages(Class
236                     .forName("com.puppycrawl.tools.checkstyle.checks.regexp.MultilineDetector"),
237                     deepScan));
238         }
239         else if (module == RegexpSinglelineCheck.class
240                 || module == RegexpSinglelineJavaCheck.class) {
241             checkstyleMessages.addAll(getCheckMessages(Class
242                     .forName("com.puppycrawl.tools.checkstyle.checks.regexp.SinglelineDetector"),
243                     deepScan));
244         }
245 
246         return checkstyleMessages;
247     }
248 
249     /**
250      * Should the class be deep scanned for messages.
251      *
252      * @param superModule The class to examine.
253      * @return {@code true} if the class should be deeply scanned.
254      */
255     private static boolean shouldScanDeepClassForMessages(Class<?> superModule) {
256         return superModule == AbstractNameCheck.class
257                 || superModule == AbstractAccessControlNameCheck.class
258                 || superModule == AbstractParenPadCheck.class
259                 || superModule == AbstractSuperCheck.class;
260     }
261 
262     /**
263      * Gets the check message 'as is' from appropriate 'messages.properties'
264      * file.
265      *
266      * @param module The package the message is located in.
267      * @param locale the locale to get the message for.
268      * @param messageKey the key of message in 'messages*.properties' file.
269      * @param arguments the arguments of message in 'messages*.properties' file.
270      * @return the check's formatted message.
271      */
272     public static String getCheckMessage(Class<?> module, Locale locale, String messageKey,
273             Object... arguments) {
274         String checkMessage;
275         try {
276             final Properties pr = new Properties();
277             if (locale == Locale.ENGLISH) {
278                 pr.load(module.getResourceAsStream("messages.properties"));
279             }
280             else {
281                 pr.load(module
282                         .getResourceAsStream("messages_" + locale.getLanguage() + ".properties"));
283             }
284             final MessageFormat formatter = new MessageFormat(pr.getProperty(messageKey), locale);
285             checkMessage = formatter.format(arguments);
286         }
287         catch (IOException ignored) {
288             checkMessage = null;
289         }
290         return checkMessage;
291     }
292 
293     public static String getTokenText(int[] tokens, int... subtractions) {
294         final String tokenText;
295         if (subtractions.length == 0 && Arrays.equals(tokens, TokenUtil.getAllTokenIds())) {
296             tokenText = "TokenTypes.";
297         }
298         else {
299             final StringBuilder result = new StringBuilder(50);
300             boolean first = true;
301 
302             for (int token : tokens) {
303                 boolean found = false;
304 
305                 for (int subtraction : subtractions) {
306                     if (subtraction == token) {
307                         found = true;
308                         break;
309                     }
310                 }
311 
312                 if (found) {
313                     continue;
314                 }
315 
316                 if (first) {
317                     first = false;
318                 }
319                 else {
320                     result.append(", ");
321                 }
322 
323                 result.append(TokenUtil.getTokenName(token));
324             }
325 
326             if (!result.isEmpty()) {
327                 result.append('.');
328             }
329 
330             tokenText = result.toString();
331         }
332         return tokenText;
333     }
334 
335     public static Set<String> getTokenNameSet(int... tokens) {
336         final Set<String> result = new HashSet<>();
337 
338         for (int token : tokens) {
339             result.add(TokenUtil.getTokenName(token));
340         }
341 
342         return result;
343     }
344 
345     public static String getJavadocTokenText(int[] tokens, int... subtractions) {
346         final StringBuilder result = new StringBuilder(50);
347         boolean first = true;
348 
349         for (int token : tokens) {
350             boolean found = false;
351 
352             for (int subtraction : subtractions) {
353                 if (subtraction == token) {
354                     found = true;
355                     break;
356                 }
357             }
358 
359             if (found) {
360                 continue;
361             }
362 
363             if (first) {
364                 first = false;
365             }
366             else {
367                 result.append(", ");
368             }
369 
370             result.append(JavadocUtil.getTokenName(token));
371         }
372 
373         if (!result.isEmpty()) {
374             result.append('.');
375         }
376 
377         return result.toString();
378     }
379 
380     public static String getLineSeparatorForFile(String filepath, Charset charset)
381             throws IOException {
382         final OptionalInt endOfLineChar = new FileText(new File(filepath), charset.name())
383                 .getFullText()
384                 .chars()
385                 .filter(character -> character == '\r' || character == '\n')
386                 .findFirst();
387 
388         final String result;
389         if (endOfLineChar.isPresent() && endOfLineChar.getAsInt() == '\r') {
390             result = CRLF;
391         }
392         else {
393             result = "\n";
394         }
395         return result;
396     }
397 }