001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2014  Oliver Burn
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////////////////////////////////////////////////////////////////////////////////
019package com.puppycrawl.tools.checkstyle.checks.coding;
020
021import com.google.common.collect.Sets;
022import com.puppycrawl.tools.checkstyle.api.DetailAST;
023import com.puppycrawl.tools.checkstyle.api.FullIdent;
024import com.puppycrawl.tools.checkstyle.api.TokenTypes;
025import com.puppycrawl.tools.checkstyle.checks.AbstractFormatCheck;
026import com.puppycrawl.tools.checkstyle.checks.CheckUtils;
027import java.util.Set;
028
029/**
030 * <p>
031 * Checks that particular class are never used as types in variable
032 * declarations, return values or parameters. Includes
033 * a pattern check that by default disallows abstract classes.
034 * </p>
035 * <p>
036 * Rationale:
037 * Helps reduce coupling on concrete classes. In addition abstract
038 * classes should be thought of a convenience base class
039 * implementations of interfaces and as such are not types themsleves.
040 * </p>
041 * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
042 */
043public final class IllegalTypeCheck extends AbstractFormatCheck
044{
045    /** Default value of pattern for illegal class name. */
046    private static final String DEFAULT_FORMAT = "^(.*[\\.])?Abstract.*$";
047    /** Abstract classes legal by default. */
048    private static final String[] DEFAULT_LEGAL_ABSTRACT_NAMES = {};
049    /** Types illegal by default. */
050    private static final String[] DEFAULT_ILLEGAL_TYPES = {
051        "GregorianCalendar",
052        "Hashtable",
053        "HashSet",
054        "HashMap",
055        "ArrayList",
056        "LinkedList",
057        "LinkedHashMap",
058        "LinkedHashSet",
059        "TreeSet",
060        "TreeMap",
061        "Vector",
062        "java.util.GregorianCalendar",
063        "java.util.Hashtable",
064        "java.util.HashSet",
065        "java.util.HashMap",
066        "java.util.ArrayList",
067        "java.util.LinkedList",
068        "java.util.LinkedHashMap",
069        "java.util.LinkedHashSet",
070        "java.util.TreeSet",
071        "java.util.TreeMap",
072        "java.util.Vector",
073    };
074
075    /** Default ignored method names. */
076    private static final String[] DEFAULT_IGNORED_METHOD_NAMES = {
077        "getInitialContext",
078        "getEnvironment",
079    };
080
081    /** illegal classes. */
082    private final Set<String> mIllegalClassNames = Sets.newHashSet();
083    /** legal abstract classes. */
084    private final Set<String> mLegalAbstractClassNames = Sets.newHashSet();
085    /** methods which should be ignored. */
086    private final Set<String> mIgnoredMethodNames = Sets.newHashSet();
087
088    /** Creates new instance of the check. */
089    public IllegalTypeCheck()
090    {
091        super(DEFAULT_FORMAT);
092        setIllegalClassNames(DEFAULT_ILLEGAL_TYPES);
093        setLegalAbstractClassNames(DEFAULT_LEGAL_ABSTRACT_NAMES);
094        setIgnoredMethodNames(DEFAULT_IGNORED_METHOD_NAMES);
095    }
096
097    @Override
098    public int[] getDefaultTokens()
099    {
100        return new int[] {
101            TokenTypes.VARIABLE_DEF,
102            TokenTypes.PARAMETER_DEF,
103            TokenTypes.METHOD_DEF,
104        };
105    }
106
107    @Override
108    public void visitToken(DetailAST aAST)
109    {
110        switch (aAST.getType()) {
111        case TokenTypes.METHOD_DEF:
112            visitMethodDef(aAST);
113            break;
114        case TokenTypes.VARIABLE_DEF:
115            visitVariableDef(aAST);
116            break;
117        case TokenTypes.PARAMETER_DEF:
118            visitParameterDef(aAST);
119            break;
120        default:
121            throw new IllegalStateException(aAST.toString());
122        }
123    }
124
125    /**
126     * Checks return type of a given method.
127     * @param aAST method for check.
128     */
129    private void visitMethodDef(DetailAST aAST)
130    {
131        if (isCheckedMethod(aAST)) {
132            checkClassName(aAST);
133        }
134    }
135
136    /**
137     * Checks type of parameters.
138     * @param aAST parameter list for check.
139     */
140    private void visitParameterDef(DetailAST aAST)
141    {
142        final DetailAST grandParentAST = aAST.getParent().getParent();
143
144        if ((grandParentAST.getType() == TokenTypes.METHOD_DEF)
145            && isCheckedMethod(grandParentAST))
146        {
147            checkClassName(aAST);
148        }
149    }
150
151    /**
152     * Checks type of given variable.
153     * @param aAST variable to check.
154     */
155    private void visitVariableDef(DetailAST aAST)
156    {
157        checkClassName(aAST);
158    }
159
160    /**
161     * Checks type of given method, parameter or variable.
162     * @param aAST node to check.
163     */
164    private void checkClassName(DetailAST aAST)
165    {
166        final DetailAST type = aAST.findFirstToken(TokenTypes.TYPE);
167        final FullIdent ident = CheckUtils.createFullType(type);
168
169        if (isMatchingClassName(ident.getText())) {
170            log(ident.getLineNo(), ident.getColumnNo(),
171                "illegal.type", ident.getText());
172        }
173    }
174
175    /**
176     * @param aClassName class name to check.
177     * @return true if given class name is one of illegal classes
178     *         or if it matches to abstract class names pattern.
179     */
180    private boolean isMatchingClassName(String aClassName)
181    {
182        return mIllegalClassNames.contains(aClassName)
183            || (!mLegalAbstractClassNames.contains(aClassName)
184                && getRegexp().matcher(aClassName).find());
185    }
186
187    /**
188     * @param aAST method def to check.
189     * @return true if we should check this method.
190     */
191    private boolean isCheckedMethod(DetailAST aAST)
192    {
193        final String methodName =
194            aAST.findFirstToken(TokenTypes.IDENT).getText();
195        return !mIgnoredMethodNames.contains(methodName);
196    }
197
198    /**
199     * Set the list of illegal variable types.
200     * @param aClassNames array of illegal variable types
201     */
202    public void setIllegalClassNames(String[] aClassNames)
203    {
204        mIllegalClassNames.clear();
205        for (String name : aClassNames) {
206            mIllegalClassNames.add(name);
207            final int lastDot = name.lastIndexOf(".");
208            if ((lastDot > 0) && (lastDot < (name.length() - 1))) {
209                final String shortName =
210                    name.substring(name.lastIndexOf(".") + 1);
211                mIllegalClassNames.add(shortName);
212            }
213        }
214    }
215
216    /**
217     * Get the list of illegal variable types.
218     * @return array of illegal variable types
219     */
220    public String[] getIllegalClassNames()
221    {
222        return mIllegalClassNames.toArray(
223            new String[mIllegalClassNames.size()]);
224    }
225
226    /**
227     * Set the list of ignore method names.
228     * @param aMethodNames array of ignored method names
229     */
230    public void setIgnoredMethodNames(String[] aMethodNames)
231    {
232        mIgnoredMethodNames.clear();
233        for (String element : aMethodNames) {
234            mIgnoredMethodNames.add(element);
235        }
236    }
237
238    /**
239     * Get the list of ignored method names.
240     * @return array of ignored method names
241     */
242    public String[] getIgnoredMethodNames()
243    {
244        return mIgnoredMethodNames.toArray(
245            new String[mIgnoredMethodNames.size()]);
246    }
247
248    /**
249     * Set the list of legal abstract class names.
250     * @param aClassNames array of legal abstract class names
251     */
252    public void setLegalAbstractClassNames(String[] aClassNames)
253    {
254        mLegalAbstractClassNames.clear();
255        for (String element : aClassNames) {
256            mLegalAbstractClassNames.add(element);
257        }
258    }
259
260    /**
261     * Get the list of legal abstract class names.
262     * @return array of legal abstract class names
263     */
264    public String[] getLegalAbstractClassNames()
265    {
266        return mLegalAbstractClassNames.toArray(
267            new String[mLegalAbstractClassNames.size()]);
268    }
269}