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