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.checks.imports;
21  
22  import com.puppycrawl.tools.checkstyle.StatelessCheck;
23  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
24  import com.puppycrawl.tools.checkstyle.api.DetailAST;
25  import com.puppycrawl.tools.checkstyle.api.FullIdent;
26  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
27  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
28  
29  /**
30   * <p>
31   * Check that finds static imports.
32   * </p>
33   * <p>
34   * Rationale: Importing static members can lead to naming conflicts
35   * between class' members. It may lead to poor code readability since it
36   * may no longer be clear what class a member resides (without looking
37   * at the import statement).
38   * </p>
39   * <p>
40   * An example of how to configure the check is:
41   * </p>
42   * <pre>
43   * &lt;module name="AvoidStaticImport"&gt;
44   *   &lt;property name="excludes"
45   *       value="java.lang.System.out,java.lang.Math.*"/&gt;
46   * &lt;/module&gt;
47   * </pre>
48   * The optional "excludes" property allows for certain classes via a star
49   * notation to be excluded such as java.lang.Math.* or specific
50   * static members to be excluded like java.lang.System.out for a variable
51   * or java.lang.Math.random for a method.
52   *
53   * <p>
54   * If you exclude a starred import on a class this automatically
55   * excludes each member individually.
56   * </p>
57   *
58   * <p>
59   * For example:
60   * Excluding java.lang.Math.* will allow the import of
61   * each static member in the Math class individually like
62   * java.lang.Math.PI
63   * </p>
64   */
65  @StatelessCheck
66  public class AvoidStaticImportCheck
67      extends AbstractCheck {
68  
69      /**
70       * A key is pointing to the warning message text in "messages.properties"
71       * file.
72       */
73      public static final String MSG_KEY = "import.avoidStatic";
74  
75      /** The classes/static members to exempt from this check. */
76      private String[] excludes = CommonUtil.EMPTY_STRING_ARRAY;
77  
78      @Override
79      public int[] getDefaultTokens() {
80          return getRequiredTokens();
81      }
82  
83      @Override
84      public int[] getAcceptableTokens() {
85          return getRequiredTokens();
86      }
87  
88      @Override
89      public int[] getRequiredTokens() {
90          return new int[] {TokenTypes.STATIC_IMPORT};
91      }
92  
93      /**
94       * Sets the list of classes or static members to be exempt from the check.
95       * @param excludes a list of fully-qualified class names/specific
96       *     static members where static imports are ok
97       */
98      public void setExcludes(String... excludes) {
99          this.excludes = excludes.clone();
100     }
101 
102     @Override
103     public void visitToken(final DetailAST ast) {
104         final DetailAST startingDot =
105             ast.getFirstChild().getNextSibling();
106         final FullIdent name = FullIdent.createFullIdent(startingDot);
107 
108         if (!isExempt(name.getText())) {
109             log(startingDot.getLineNo(), MSG_KEY, name.getText());
110         }
111     }
112 
113     /**
114      * Checks if a class or static member is exempt from known excludes.
115      *
116      * @param classOrStaticMember
117      *                the class or static member
118      * @return true if except false if not
119      */
120     private boolean isExempt(String classOrStaticMember) {
121         boolean exempt = false;
122 
123         for (String exclude : excludes) {
124             if (classOrStaticMember.equals(exclude)
125                     || isStarImportOfPackage(classOrStaticMember, exclude)) {
126                 exempt = true;
127                 break;
128             }
129         }
130         return exempt;
131     }
132 
133     /**
134      * Returns true if classOrStaticMember is a starred name of package,
135      *  not just member name.
136      * @param classOrStaticMember - full name of member
137      * @param exclude - current exclusion
138      * @return true if member in exclusion list
139      */
140     private static boolean isStarImportOfPackage(String classOrStaticMember, String exclude) {
141         boolean result = false;
142         if (exclude.endsWith(".*")) {
143             //this section allows explicit imports
144             //to be exempt when configured using
145             //a starred import
146             final String excludeMinusDotStar =
147                 exclude.substring(0, exclude.length() - 2);
148             if (classOrStaticMember.startsWith(excludeMinusDotStar)
149                     && !classOrStaticMember.equals(excludeMinusDotStar)) {
150                 final String member = classOrStaticMember.substring(
151                         excludeMinusDotStar.length() + 1);
152                 //if it contains a dot then it is not a member but a package
153                 if (member.indexOf('.') == -1) {
154                     result = true;
155                 }
156             }
157         }
158         return result;
159     }
160 
161 }