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