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 java.util.ArrayList;
23  import java.util.List;
24  
25  import com.puppycrawl.tools.checkstyle.StatelessCheck;
26  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
27  import com.puppycrawl.tools.checkstyle.api.DetailAST;
28  import com.puppycrawl.tools.checkstyle.api.FullIdent;
29  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
30  
31  /**
32   * <p>
33   * Check that finds import statements that use the * notation.
34   * </p>
35   * <p>
36   * Rationale: Importing all classes from a package or static
37   * members from a class leads to tight coupling between packages
38   * or classes and might lead to problems when a new version of a
39   * library introduces name clashes.
40   * </p>
41   * <p>
42   * An example of how to configure the check is:
43   * </p>
44   * <pre>
45   * &lt;module name="AvoidStarImport"&gt;
46   *   &lt;property name="excludes" value="java.io,java.net,java.lang.Math"/&gt;
47   *   &lt;property name="allowClassImports" value="false"/&gt;
48   *   &lt;property name="allowStaticMemberImports" value="false"/&gt;
49   * &lt;/module&gt;
50   * </pre>
51   * The optional "excludes" property allows for certain packages like
52   * java.io or java.net to be exempted from the rule. It also is used to
53   * allow certain classes like java.lang.Math or java.io.File to be
54   * excluded in order to support static member imports.
55   *
56   * <p>The optional "allowClassImports" when set to true, will allow starred
57   * class imports but will not affect static member imports.
58   *
59   * <p>The optional "allowStaticMemberImports" when set to true will allow
60   * starred static member imports but will not affect class imports.
61   *
62   */
63  @StatelessCheck
64  public class AvoidStarImportCheck
65      extends AbstractCheck {
66  
67      /**
68       * A key is pointing to the warning message text in "messages.properties"
69       * file.
70       */
71      public static final String MSG_KEY = "import.avoidStar";
72  
73      /** Suffix for the star import. */
74      private static final String STAR_IMPORT_SUFFIX = ".*";
75  
76      /** The packages/classes to exempt from this check. */
77      private final List<String> excludes = new ArrayList<>();
78  
79      /** Whether to allow all class imports. */
80      private boolean allowClassImports;
81  
82      /** Whether to allow all static member imports. */
83      private boolean allowStaticMemberImports;
84  
85      @Override
86      public int[] getDefaultTokens() {
87          return getRequiredTokens();
88      }
89  
90      @Override
91      public int[] getAcceptableTokens() {
92          return getRequiredTokens();
93      }
94  
95      @Override
96      public int[] getRequiredTokens() {
97          // original implementation checks both IMPORT and STATIC_IMPORT tokens to avoid ".*" imports
98          // however user can allow using "import" or "import static"
99          // by configuring allowClassImports and allowStaticMemberImports
100         // To avoid potential confusion when user specifies conflicting options on configuration
101         // (see example below) we are adding both tokens to Required list
102         //   <module name="AvoidStarImport">
103         //      <property name="tokens" value="IMPORT"/>
104         //      <property name="allowStaticMemberImports" value="false"/>
105         //   </module>
106         return new int[] {TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT};
107     }
108 
109     /**
110      * Sets the list of packages or classes to be exempt from the check.
111      * The excludes can contain a .* or not.
112      * @param excludesParam a list of package names/fully-qualifies class names
113      *     where star imports are ok.
114      */
115     public void setExcludes(String... excludesParam) {
116         for (final String exclude : excludesParam) {
117             if (exclude.endsWith(STAR_IMPORT_SUFFIX)) {
118                 excludes.add(exclude);
119             }
120             else {
121                 excludes.add(exclude + STAR_IMPORT_SUFFIX);
122             }
123         }
124     }
125 
126     /**
127      * Sets whether or not to allow all non-static class imports.
128      * @param allow true to allow false to disallow
129      */
130     public void setAllowClassImports(boolean allow) {
131         allowClassImports = allow;
132     }
133 
134     /**
135      * Sets whether or not to allow all static member imports.
136      * @param allow true to allow false to disallow
137      */
138     public void setAllowStaticMemberImports(boolean allow) {
139         allowStaticMemberImports = allow;
140     }
141 
142     @Override
143     public void visitToken(final DetailAST ast) {
144         if (!allowClassImports && ast.getType() == TokenTypes.IMPORT) {
145             final DetailAST startingDot = ast.getFirstChild();
146             logsStarredImportViolation(startingDot);
147         }
148         else if (!allowStaticMemberImports
149             && ast.getType() == TokenTypes.STATIC_IMPORT) {
150             // must navigate past the static keyword
151             final DetailAST startingDot = ast.getFirstChild().getNextSibling();
152             logsStarredImportViolation(startingDot);
153         }
154     }
155 
156     /**
157      * Gets the full import identifier.  If the import is a starred import and
158      * it's not excluded then a violation is logged.
159      * @param startingDot the starting dot for the import statement
160      */
161     private void logsStarredImportViolation(DetailAST startingDot) {
162         final FullIdent name = FullIdent.createFullIdent(startingDot);
163         final String importText = name.getText();
164         if (importText.endsWith(STAR_IMPORT_SUFFIX) && !excludes.contains(importText)) {
165             log(startingDot.getLineNo(), MSG_KEY, importText);
166         }
167     }
168 
169 }