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.utils;
21  
22  import java.util.ArrayList;
23  import java.util.HashSet;
24  import java.util.List;
25  import java.util.Set;
26  import java.util.regex.Pattern;
27  
28  import antlr.collections.AST;
29  import com.puppycrawl.tools.checkstyle.api.DetailAST;
30  import com.puppycrawl.tools.checkstyle.api.FullIdent;
31  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
32  import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifier;
33  
34  /**
35   * Contains utility methods for the checks.
36   *
37   * @author Oliver Burn
38   * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
39   * @author o_sukhodolsky
40   */
41  public final class CheckUtils {
42      // constants for parseDouble()
43      /** Octal radix. */
44      private static final int BASE_8 = 8;
45  
46      /** Decimal radix. */
47      private static final int BASE_10 = 10;
48  
49      /** Hex radix. */
50      private static final int BASE_16 = 16;
51  
52      /** Maximum children allowed in setter/getter. */
53      private static final int SETTER_GETTER_MAX_CHILDREN = 7;
54  
55      /** Maximum nodes allowed in a body of setter. */
56      private static final int SETTER_BODY_SIZE = 3;
57  
58      /** Maximum nodes allowed in a body of getter. */
59      private static final int GETTER_BODY_SIZE = 2;
60  
61      /** Pattern matching underscore characters ('_'). */
62      private static final Pattern UNDERSCORE_PATTERN = Pattern.compile("_");
63  
64      /** Pattern matching names of setter methods. */
65      private static final Pattern SETTER_PATTERN = Pattern.compile("^set[A-Z].*");
66  
67      /** Pattern matching names of getter methods. */
68      private static final Pattern GETTER_PATTERN = Pattern.compile("^(is|get)[A-Z].*");
69  
70      /** Prevent instances. */
71      private CheckUtils() {
72      }
73  
74      /**
75       * Creates {@code FullIdent} for given type node.
76       * @param typeAST a type node.
77       * @return {@code FullIdent} for given type.
78       */
79      public static FullIdent createFullType(final DetailAST typeAST) {
80          DetailAST ast = typeAST;
81  
82          // ignore array part of type
83          while (ast.findFirstToken(TokenTypes.ARRAY_DECLARATOR) != null) {
84              ast = ast.findFirstToken(TokenTypes.ARRAY_DECLARATOR);
85          }
86  
87          return FullIdent.createFullIdent(ast.getFirstChild());
88      }
89  
90      /**
91       * Tests whether a method definition AST defines an equals covariant.
92       * @param ast the method definition AST to test.
93       *     Precondition: ast is a TokenTypes.METHOD_DEF node.
94       * @return true if ast defines an equals covariant.
95       */
96      public static boolean isEqualsMethod(DetailAST ast) {
97          boolean equalsMethod = false;
98  
99          if (ast.getType() == TokenTypes.METHOD_DEF) {
100             final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
101             final boolean staticOrAbstract =
102                     modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null
103                     || modifiers.findFirstToken(TokenTypes.ABSTRACT) != null;
104 
105             if (!staticOrAbstract) {
106                 final DetailAST nameNode = ast.findFirstToken(TokenTypes.IDENT);
107                 final String name = nameNode.getText();
108 
109                 if ("equals".equals(name)) {
110                     // one parameter?
111                     final DetailAST paramsNode = ast.findFirstToken(TokenTypes.PARAMETERS);
112                     equalsMethod = paramsNode.getChildCount() == 1;
113                 }
114             }
115         }
116         return equalsMethod;
117     }
118 
119     /**
120      * Returns whether a token represents an ELSE as part of an ELSE / IF set.
121      * @param ast the token to check
122      * @return whether it is
123      */
124     public static boolean isElseIf(DetailAST ast) {
125         final DetailAST parentAST = ast.getParent();
126 
127         return ast.getType() == TokenTypes.LITERAL_IF
128             && (isElse(parentAST) || isElseWithCurlyBraces(parentAST));
129     }
130 
131     /**
132      * Returns whether a token represents an ELSE.
133      * @param ast the token to check
134      * @return whether the token represents an ELSE
135      */
136     private static boolean isElse(DetailAST ast) {
137         return ast.getType() == TokenTypes.LITERAL_ELSE;
138     }
139 
140     /**
141      * Returns whether a token represents an SLIST as part of an ELSE
142      * statement.
143      * @param ast the token to check
144      * @return whether the toke does represent an SLIST as part of an ELSE
145      */
146     private static boolean isElseWithCurlyBraces(DetailAST ast) {
147         return ast.getType() == TokenTypes.SLIST
148             && ast.getChildCount() == 2
149             && isElse(ast.getParent());
150     }
151 
152     /**
153      * Returns the value represented by the specified string of the specified
154      * type. Returns 0 for types other than float, double, int, and long.
155      * @param text the string to be parsed.
156      * @param type the token type of the text. Should be a constant of
157      * {@link TokenTypes}.
158      * @return the double value represented by the string argument.
159      */
160     public static double parseDouble(String text, int type) {
161         String txt = UNDERSCORE_PATTERN.matcher(text).replaceAll("");
162         double result = 0;
163         switch (type) {
164             case TokenTypes.NUM_FLOAT:
165             case TokenTypes.NUM_DOUBLE:
166                 result = Double.parseDouble(txt);
167                 break;
168             case TokenTypes.NUM_INT:
169             case TokenTypes.NUM_LONG:
170                 int radix = BASE_10;
171                 if (txt.startsWith("0x") || txt.startsWith("0X")) {
172                     radix = BASE_16;
173                     txt = txt.substring(2);
174                 }
175                 else if (txt.charAt(0) == '0') {
176                     radix = BASE_8;
177                     txt = txt.substring(1);
178                 }
179                 if (CommonUtils.endsWithChar(txt, 'L') || CommonUtils.endsWithChar(txt, 'l')) {
180                     txt = txt.substring(0, txt.length() - 1);
181                 }
182                 if (!txt.isEmpty()) {
183                     if (type == TokenTypes.NUM_INT) {
184                         result = parseInt(txt, radix);
185                     }
186                     else {
187                         result = parseLong(txt, radix);
188                     }
189                 }
190                 break;
191             default:
192                 break;
193         }
194         return result;
195     }
196 
197     /**
198      * Parses the string argument as a signed integer in the radix specified by
199      * the second argument. The characters in the string must all be digits of
200      * the specified radix. Handles negative values, which method
201      * java.lang.Integer.parseInt(String, int) does not.
202      * @param text the String containing the integer representation to be
203      *     parsed. Precondition: text contains a parsable int.
204      * @param radix the radix to be used while parsing text.
205      * @return the integer represented by the string argument in the specified radix.
206      */
207     private static int parseInt(String text, int radix) {
208         int result = 0;
209         final int max = text.length();
210         for (int i = 0; i < max; i++) {
211             final int digit = Character.digit(text.charAt(i), radix);
212             result *= radix;
213             result += digit;
214         }
215         return result;
216     }
217 
218     /**
219      * Parses the string argument as a signed long in the radix specified by
220      * the second argument. The characters in the string must all be digits of
221      * the specified radix. Handles negative values, which method
222      * java.lang.Integer.parseInt(String, int) does not.
223      * @param text the String containing the integer representation to be
224      *     parsed. Precondition: text contains a parsable int.
225      * @param radix the radix to be used while parsing text.
226      * @return the long represented by the string argument in the specified radix.
227      */
228     private static long parseLong(String text, int radix) {
229         long result = 0;
230         final int max = text.length();
231         for (int i = 0; i < max; i++) {
232             final int digit = Character.digit(text.charAt(i), radix);
233             result *= radix;
234             result += digit;
235         }
236         return result;
237     }
238 
239     /**
240      * Finds sub-node for given node minimal (line, column) pair.
241      * @param node the root of tree for search.
242      * @return sub-node with minimal (line, column) pair.
243      */
244     public static DetailAST getFirstNode(final DetailAST node) {
245         DetailAST currentNode = node;
246         DetailAST child = node.getFirstChild();
247         while (child != null) {
248             final DetailAST newNode = getFirstNode(child);
249             if (newNode.getLineNo() < currentNode.getLineNo()
250                 || newNode.getLineNo() == currentNode.getLineNo()
251                     && newNode.getColumnNo() < currentNode.getColumnNo()) {
252                 currentNode = newNode;
253             }
254             child = child.getNextSibling();
255         }
256 
257         return currentNode;
258     }
259 
260     /**
261      * Retrieves the names of the type parameters to the node.
262      * @param node the parameterized AST node
263      * @return a list of type parameter names
264      */
265     public static List<String> getTypeParameterNames(final DetailAST node) {
266         final DetailAST typeParameters =
267             node.findFirstToken(TokenTypes.TYPE_PARAMETERS);
268 
269         final List<String> typeParameterNames = new ArrayList<>();
270         if (typeParameters != null) {
271             final DetailAST typeParam =
272                 typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER);
273             typeParameterNames.add(
274                     typeParam.findFirstToken(TokenTypes.IDENT).getText());
275 
276             DetailAST sibling = typeParam.getNextSibling();
277             while (sibling != null) {
278                 if (sibling.getType() == TokenTypes.TYPE_PARAMETER) {
279                     typeParameterNames.add(
280                             sibling.findFirstToken(TokenTypes.IDENT).getText());
281                 }
282                 sibling = sibling.getNextSibling();
283             }
284         }
285 
286         return typeParameterNames;
287     }
288 
289     /**
290      * Retrieves the type parameters to the node.
291      * @param node the parameterized AST node
292      * @return a list of type parameter names
293      */
294     public static List<DetailAST> getTypeParameters(final DetailAST node) {
295         final DetailAST typeParameters =
296             node.findFirstToken(TokenTypes.TYPE_PARAMETERS);
297 
298         final List<DetailAST> typeParams = new ArrayList<>();
299         if (typeParameters != null) {
300             final DetailAST typeParam =
301                 typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER);
302             typeParams.add(typeParam);
303 
304             DetailAST sibling = typeParam.getNextSibling();
305             while (sibling != null) {
306                 if (sibling.getType() == TokenTypes.TYPE_PARAMETER) {
307                     typeParams.add(sibling);
308                 }
309                 sibling = sibling.getNextSibling();
310             }
311         }
312 
313         return typeParams;
314     }
315 
316     /**
317      * Returns whether an AST represents a setter method.
318      * @param ast the AST to check with
319      * @return whether the AST represents a setter method
320      */
321     public static boolean isSetterMethod(final DetailAST ast) {
322         boolean setterMethod = false;
323 
324         // Check have a method with exactly 7 children which are all that
325         // is allowed in a proper setter method which does not throw any
326         // exceptions.
327         if (ast.getType() == TokenTypes.METHOD_DEF
328                 && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) {
329 
330             final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
331             final String name = type.getNextSibling().getText();
332             final boolean matchesSetterFormat = SETTER_PATTERN.matcher(name).matches();
333             final boolean voidReturnType = type.findFirstToken(TokenTypes.LITERAL_VOID) != null;
334 
335             final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
336             final boolean singleParam = params.getChildCount(TokenTypes.PARAMETER_DEF) == 1;
337 
338             if (matchesSetterFormat && voidReturnType && singleParam) {
339                 // Now verify that the body consists of:
340                 // SLIST -> EXPR -> ASSIGN
341                 // SEMI
342                 // RCURLY
343                 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
344 
345                 if (slist != null && slist.getChildCount() == SETTER_BODY_SIZE) {
346                     final DetailAST expr = slist.getFirstChild();
347                     setterMethod = expr.getFirstChild().getType() == TokenTypes.ASSIGN;
348                 }
349             }
350         }
351         return setterMethod;
352     }
353 
354     /**
355      * Returns whether an AST represents a getter method.
356      * @param ast the AST to check with
357      * @return whether the AST represents a getter method
358      */
359     public static boolean isGetterMethod(final DetailAST ast) {
360         boolean getterMethod = false;
361 
362         // Check have a method with exactly 7 children which are all that
363         // is allowed in a proper getter method which does not throw any
364         // exceptions.
365         if (ast.getType() == TokenTypes.METHOD_DEF
366                 && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) {
367 
368             final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
369             final String name = type.getNextSibling().getText();
370             final boolean matchesGetterFormat = GETTER_PATTERN.matcher(name).matches();
371             final boolean noVoidReturnType = type.findFirstToken(TokenTypes.LITERAL_VOID) == null;
372 
373             final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
374             final boolean noParams = params.getChildCount(TokenTypes.PARAMETER_DEF) == 0;
375 
376             if (matchesGetterFormat && noVoidReturnType && noParams) {
377                 // Now verify that the body consists of:
378                 // SLIST -> RETURN
379                 // RCURLY
380                 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
381 
382                 if (slist != null && slist.getChildCount() == GETTER_BODY_SIZE) {
383                     final DetailAST expr = slist.getFirstChild();
384                     getterMethod = expr.getType() == TokenTypes.LITERAL_RETURN;
385                 }
386             }
387         }
388         return getterMethod;
389     }
390 
391     /**
392      * Checks whether a method is a not void one.
393      *
394      * @param methodDefAst the method node.
395      * @return true if method is a not void one.
396      */
397     public static boolean isNonVoidMethod(DetailAST methodDefAst) {
398         boolean returnValue = false;
399         if (methodDefAst.getType() == TokenTypes.METHOD_DEF) {
400             final DetailAST typeAST = methodDefAst.findFirstToken(TokenTypes.TYPE);
401             if (typeAST.findFirstToken(TokenTypes.LITERAL_VOID) == null) {
402                 returnValue = true;
403             }
404         }
405         return returnValue;
406     }
407 
408     /**
409      * Checks whether a parameter is a receiver.
410      *
411      * @param parameterDefAst the parameter node.
412      * @return true if the parameter is a receiver.
413      */
414     public static boolean isReceiverParameter(DetailAST parameterDefAst) {
415         return parameterDefAst.getType() == TokenTypes.PARAMETER_DEF
416                 && parameterDefAst.findFirstToken(TokenTypes.IDENT) == null;
417     }
418 
419     /**
420      * Returns {@link AccessModifier} based on the information about access modifier
421      * taken from the given token of type {@link TokenTypes#MODIFIERS}.
422      * @param modifiersToken token of type {@link TokenTypes#MODIFIERS}.
423      * @return {@link AccessModifier}.
424      */
425     public static AccessModifier getAccessModifierFromModifiersToken(DetailAST modifiersToken) {
426         if (modifiersToken == null || modifiersToken.getType() != TokenTypes.MODIFIERS) {
427             throw new IllegalArgumentException("expected non-null AST-token with type 'MODIFIERS'");
428         }
429 
430         // default access modifier
431         AccessModifier accessModifier = AccessModifier.PACKAGE;
432         for (AST token = modifiersToken.getFirstChild(); token != null;
433              token = token.getNextSibling()) {
434 
435             final int tokenType = token.getType();
436             if (tokenType == TokenTypes.LITERAL_PUBLIC) {
437                 accessModifier = AccessModifier.PUBLIC;
438             }
439             else if (tokenType == TokenTypes.LITERAL_PROTECTED) {
440                 accessModifier = AccessModifier.PROTECTED;
441             }
442             else if (tokenType == TokenTypes.LITERAL_PRIVATE) {
443                 accessModifier = AccessModifier.PRIVATE;
444             }
445         }
446         return accessModifier;
447     }
448 
449     /**
450      * Create set of class names and short class names.
451      *
452      * @param classNames array of class names.
453      * @return set of class names and short class names.
454      */
455     public static Set<String> parseClassNames(String... classNames) {
456         final Set<String> illegalClassNames = new HashSet<>();
457         for (final String name : classNames) {
458             illegalClassNames.add(name);
459             final int lastDot = name.lastIndexOf('.');
460             if (lastDot != -1 && lastDot < name.length() - 1) {
461                 final String shortName = name
462                         .substring(name.lastIndexOf('.') + 1);
463                 illegalClassNames.add(shortName);
464             }
465         }
466         return illegalClassNames;
467     }
468 }