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