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.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 com.puppycrawl.tools.checkstyle.api.DetailAST;
29  import com.puppycrawl.tools.checkstyle.api.FullIdent;
30  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
31  import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifier;
32  
33  /**
34   * Contains utility methods for the checks.
35   *
36   */
37  public final class CheckUtil {
38  
39      // constants for parseDouble()
40      /** Binary radix. */
41      private static final int BASE_2 = 2;
42  
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 CheckUtil() {
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         final double result;
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.startsWith("0b") || txt.startsWith("0B")) {
176                     radix = BASE_2;
177                     txt = txt.substring(2);
178                 }
179                 else if (CommonUtil.startsWithChar(txt, '0')) {
180                     radix = BASE_8;
181                     txt = txt.substring(1);
182                 }
183                 result = parseNumber(txt, radix, type);
184                 break;
185             default:
186                 result = Double.NaN;
187                 break;
188         }
189         return result;
190     }
191 
192     /**
193      * Parses the string argument as an integer or a long in the radix specified by
194      * the second argument. The characters in the string must all be digits of
195      * the specified radix.
196      * @param text the String containing the integer representation to be
197      *     parsed. Precondition: text contains a parsable int.
198      * @param radix the radix to be used while parsing text.
199      * @param type the token type of the text. Should be a constant of
200      *     {@link TokenTypes}.
201      * @return the number represented by the string argument in the specified radix.
202      */
203     private static double parseNumber(final String text, final int radix, final int type) {
204         String txt = text;
205         if (CommonUtil.endsWithChar(txt, 'L') || CommonUtil.endsWithChar(txt, 'l')) {
206             txt = txt.substring(0, txt.length() - 1);
207         }
208         final double result;
209         if (txt.isEmpty()) {
210             result = 0.0;
211         }
212         else {
213             final boolean negative = txt.charAt(0) == '-';
214             if (type == TokenTypes.NUM_INT) {
215                 if (negative) {
216                     result = Integer.parseInt(txt, radix);
217                 }
218                 else {
219                     result = Integer.parseUnsignedInt(txt, radix);
220                 }
221             }
222             else {
223                 if (negative) {
224                     result = Long.parseLong(txt, radix);
225                 }
226                 else {
227                     result = Long.parseUnsignedLong(txt, radix);
228                 }
229             }
230         }
231         return result;
232     }
233 
234     /**
235      * Finds sub-node for given node minimal (line, column) pair.
236      * @param node the root of tree for search.
237      * @return sub-node with minimal (line, column) pair.
238      */
239     public static DetailAST getFirstNode(final DetailAST node) {
240         DetailAST currentNode = node;
241         DetailAST child = node.getFirstChild();
242         while (child != null) {
243             final DetailAST newNode = getFirstNode(child);
244             if (newNode.getLineNo() < currentNode.getLineNo()
245                 || newNode.getLineNo() == currentNode.getLineNo()
246                     && newNode.getColumnNo() < currentNode.getColumnNo()) {
247                 currentNode = newNode;
248             }
249             child = child.getNextSibling();
250         }
251 
252         return currentNode;
253     }
254 
255     /**
256      * Retrieves the names of the type parameters to the node.
257      * @param node the parameterized AST node
258      * @return a list of type parameter names
259      */
260     public static List<String> getTypeParameterNames(final DetailAST node) {
261         final DetailAST typeParameters =
262             node.findFirstToken(TokenTypes.TYPE_PARAMETERS);
263 
264         final List<String> typeParameterNames = new ArrayList<>();
265         if (typeParameters != null) {
266             final DetailAST typeParam =
267                 typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER);
268             typeParameterNames.add(
269                     typeParam.findFirstToken(TokenTypes.IDENT).getText());
270 
271             DetailAST sibling = typeParam.getNextSibling();
272             while (sibling != null) {
273                 if (sibling.getType() == TokenTypes.TYPE_PARAMETER) {
274                     typeParameterNames.add(
275                             sibling.findFirstToken(TokenTypes.IDENT).getText());
276                 }
277                 sibling = sibling.getNextSibling();
278             }
279         }
280 
281         return typeParameterNames;
282     }
283 
284     /**
285      * Retrieves the type parameters to the node.
286      * @param node the parameterized AST node
287      * @return a list of type parameter names
288      */
289     public static List<DetailAST> getTypeParameters(final DetailAST node) {
290         final DetailAST typeParameters =
291             node.findFirstToken(TokenTypes.TYPE_PARAMETERS);
292 
293         final List<DetailAST> typeParams = new ArrayList<>();
294         if (typeParameters != null) {
295             final DetailAST typeParam =
296                 typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER);
297             typeParams.add(typeParam);
298 
299             DetailAST sibling = typeParam.getNextSibling();
300             while (sibling != null) {
301                 if (sibling.getType() == TokenTypes.TYPE_PARAMETER) {
302                     typeParams.add(sibling);
303                 }
304                 sibling = sibling.getNextSibling();
305             }
306         }
307 
308         return typeParams;
309     }
310 
311     /**
312      * Returns whether an AST represents a setter method.
313      * @param ast the AST to check with
314      * @return whether the AST represents a setter method
315      */
316     public static boolean isSetterMethod(final DetailAST ast) {
317         boolean setterMethod = false;
318 
319         // Check have a method with exactly 7 children which are all that
320         // is allowed in a proper setter method which does not throw any
321         // exceptions.
322         if (ast.getType() == TokenTypes.METHOD_DEF
323                 && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) {
324             final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
325             final String name = type.getNextSibling().getText();
326             final boolean matchesSetterFormat = SETTER_PATTERN.matcher(name).matches();
327             final boolean voidReturnType = type.findFirstToken(TokenTypes.LITERAL_VOID) != null;
328 
329             final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
330             final boolean singleParam = params.getChildCount(TokenTypes.PARAMETER_DEF) == 1;
331 
332             if (matchesSetterFormat && voidReturnType && singleParam) {
333                 // Now verify that the body consists of:
334                 // SLIST -> EXPR -> ASSIGN
335                 // SEMI
336                 // RCURLY
337                 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
338 
339                 if (slist != null && slist.getChildCount() == SETTER_BODY_SIZE) {
340                     final DetailAST expr = slist.getFirstChild();
341                     setterMethod = expr.getFirstChild().getType() == TokenTypes.ASSIGN;
342                 }
343             }
344         }
345         return setterMethod;
346     }
347 
348     /**
349      * Returns whether an AST represents a getter method.
350      * @param ast the AST to check with
351      * @return whether the AST represents a getter method
352      */
353     public static boolean isGetterMethod(final DetailAST ast) {
354         boolean getterMethod = false;
355 
356         // Check have a method with exactly 7 children which are all that
357         // is allowed in a proper getter method which does not throw any
358         // exceptions.
359         if (ast.getType() == TokenTypes.METHOD_DEF
360                 && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) {
361             final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
362             final String name = type.getNextSibling().getText();
363             final boolean matchesGetterFormat = GETTER_PATTERN.matcher(name).matches();
364             final boolean noVoidReturnType = type.findFirstToken(TokenTypes.LITERAL_VOID) == null;
365 
366             final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
367             final boolean noParams = params.getChildCount(TokenTypes.PARAMETER_DEF) == 0;
368 
369             if (matchesGetterFormat && noVoidReturnType && noParams) {
370                 // Now verify that the body consists of:
371                 // SLIST -> RETURN
372                 // RCURLY
373                 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
374 
375                 if (slist != null && slist.getChildCount() == GETTER_BODY_SIZE) {
376                     final DetailAST expr = slist.getFirstChild();
377                     getterMethod = expr.getType() == TokenTypes.LITERAL_RETURN;
378                 }
379             }
380         }
381         return getterMethod;
382     }
383 
384     /**
385      * Checks whether a method is a not void one.
386      *
387      * @param methodDefAst the method node.
388      * @return true if method is a not void one.
389      */
390     public static boolean isNonVoidMethod(DetailAST methodDefAst) {
391         boolean returnValue = false;
392         if (methodDefAst.getType() == TokenTypes.METHOD_DEF) {
393             final DetailAST typeAST = methodDefAst.findFirstToken(TokenTypes.TYPE);
394             if (typeAST.findFirstToken(TokenTypes.LITERAL_VOID) == null) {
395                 returnValue = true;
396             }
397         }
398         return returnValue;
399     }
400 
401     /**
402      * Checks whether a parameter is a receiver.
403      *
404      * @param parameterDefAst the parameter node.
405      * @return true if the parameter is a receiver.
406      */
407     public static boolean isReceiverParameter(DetailAST parameterDefAst) {
408         return parameterDefAst.getType() == TokenTypes.PARAMETER_DEF
409                 && parameterDefAst.findFirstToken(TokenTypes.IDENT) == null;
410     }
411 
412     /**
413      * Returns {@link AccessModifier} based on the information about access modifier
414      * taken from the given token of type {@link TokenTypes#MODIFIERS}.
415      * @param modifiersToken token of type {@link TokenTypes#MODIFIERS}.
416      * @return {@link AccessModifier}.
417      */
418     public static AccessModifier getAccessModifierFromModifiersToken(DetailAST modifiersToken) {
419         if (modifiersToken == null || modifiersToken.getType() != TokenTypes.MODIFIERS) {
420             throw new IllegalArgumentException("expected non-null AST-token with type 'MODIFIERS'");
421         }
422 
423         // default access modifier
424         AccessModifier accessModifier = AccessModifier.PACKAGE;
425         for (DetailAST token = modifiersToken.getFirstChild(); token != null;
426              token = token.getNextSibling()) {
427             final int tokenType = token.getType();
428             if (tokenType == TokenTypes.LITERAL_PUBLIC) {
429                 accessModifier = AccessModifier.PUBLIC;
430             }
431             else if (tokenType == TokenTypes.LITERAL_PROTECTED) {
432                 accessModifier = AccessModifier.PROTECTED;
433             }
434             else if (tokenType == TokenTypes.LITERAL_PRIVATE) {
435                 accessModifier = AccessModifier.PRIVATE;
436             }
437         }
438         return accessModifier;
439     }
440 
441     /**
442      * Create set of class names and short class names.
443      *
444      * @param classNames array of class names.
445      * @return set of class names and short class names.
446      */
447     public static Set<String> parseClassNames(String... classNames) {
448         final Set<String> illegalClassNames = new HashSet<>();
449         for (final String name : classNames) {
450             illegalClassNames.add(name);
451             final int lastDot = name.lastIndexOf('.');
452             if (lastDot != -1 && lastDot < name.length() - 1) {
453                 final String shortName = name
454                         .substring(name.lastIndexOf('.') + 1);
455                 illegalClassNames.add(shortName);
456             }
457         }
458         return illegalClassNames;
459     }
460 
461 }