001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2018 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.imports;
021
022import com.puppycrawl.tools.checkstyle.StatelessCheck;
023import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
024import com.puppycrawl.tools.checkstyle.api.DetailAST;
025import com.puppycrawl.tools.checkstyle.api.FullIdent;
026import com.puppycrawl.tools.checkstyle.api.TokenTypes;
027import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
028
029/**
030 * <p>
031 * Check that finds static imports.
032 * </p>
033 * <p>
034 * Rationale: Importing static members can lead to naming conflicts
035 * between class' members. It may lead to poor code readability since it
036 * may no longer be clear what class a member resides (without looking
037 * at the import statement).
038 * </p>
039 * <p>
040 * An example of how to configure the check is:
041 * </p>
042 * <pre>
043 * &lt;module name="AvoidStaticImport"&gt;
044 *   &lt;property name="excludes"
045 *       value="java.lang.System.out,java.lang.Math.*"/&gt;
046 * &lt;/module&gt;
047 * </pre>
048 * The optional "excludes" property allows for certain classes via a star
049 * notation to be excluded such as java.lang.Math.* or specific
050 * static members to be excluded like java.lang.System.out for a variable
051 * or java.lang.Math.random for a method.
052 *
053 * <p>
054 * If you exclude a starred import on a class this automatically
055 * excludes each member individually.
056 * </p>
057 *
058 * <p>
059 * For example:
060 * Excluding java.lang.Math.* will allow the import of
061 * each static member in the Math class individually like
062 * java.lang.Math.PI
063 * </p>
064 */
065@StatelessCheck
066public class AvoidStaticImportCheck
067    extends AbstractCheck {
068
069    /**
070     * A key is pointing to the warning message text in "messages.properties"
071     * file.
072     */
073    public static final String MSG_KEY = "import.avoidStatic";
074
075    /** The classes/static members to exempt from this check. */
076    private String[] excludes = CommonUtils.EMPTY_STRING_ARRAY;
077
078    @Override
079    public int[] getDefaultTokens() {
080        return getRequiredTokens();
081    }
082
083    @Override
084    public int[] getAcceptableTokens() {
085        return getRequiredTokens();
086    }
087
088    @Override
089    public int[] getRequiredTokens() {
090        return new int[] {TokenTypes.STATIC_IMPORT};
091    }
092
093    /**
094     * Sets the list of classes or static members to be exempt from the check.
095     * @param excludes a list of fully-qualified class names/specific
096     *     static members where static imports are ok
097     */
098    public void setExcludes(String... excludes) {
099        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}