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.metrics;
020
021import com.google.common.collect.ImmutableSet;
022import com.google.common.collect.Sets;
023import com.puppycrawl.tools.checkstyle.api.Check;
024import com.puppycrawl.tools.checkstyle.api.DetailAST;
025import com.puppycrawl.tools.checkstyle.api.FastStack;
026import com.puppycrawl.tools.checkstyle.api.FullIdent;
027import com.puppycrawl.tools.checkstyle.api.TokenTypes;
028import com.puppycrawl.tools.checkstyle.checks.CheckUtils;
029
030import java.util.Set;
031
032/**
033 * Base class for coupling calculation.
034 *
035 * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
036 * @author o_sukhodolsky
037 */
038public abstract class AbstractClassCouplingCheck extends Check
039{
040    /** Class names to ignore. */
041    private static final Set<String> DEFAULT_EXCLUDED_CLASSES =
042                ImmutableSet.<String>builder()
043                // primitives
044                .add("boolean", "byte", "char", "double", "float", "int")
045                .add("long", "short", "void")
046                // wrappers
047                .add("Boolean", "Byte", "Character", "Double", "Float")
048                .add("Integer", "Long", "Short", "Void")
049                // java.lang.*
050                .add("Object", "Class")
051                .add("String", "StringBuffer", "StringBuilder")
052                // Exceptions
053                .add("ArrayIndexOutOfBoundsException", "Exception")
054                .add("RuntimeException", "IllegalArgumentException")
055                .add("IllegalStateException", "IndexOutOfBoundsException")
056                .add("NullPointerException", "Throwable", "SecurityException")
057                .add("UnsupportedOperationException")
058                // java.util.*
059                .add("List", "ArrayList", "Deque", "Queue", "LinkedList")
060                .add("Set", "HashSet", "SortedSet", "TreeSet")
061                .add("Map", "HashMap", "SortedMap", "TreeMap")
062                .build();
063    /** User-configured class names to ignore. */
064    private Set<String> mExcludedClasses = DEFAULT_EXCLUDED_CLASSES;
065    /** Allowed complexity. */
066    private int mMax;
067    /** package of the file we check. */
068    private String mPackageName;
069
070    /** Stack of contexts. */
071    private final FastStack<Context> mContextStack = FastStack.newInstance();
072    /** Current context. */
073    private Context mContext;
074
075    /**
076     * Creates new instance of the check.
077     * @param aDefaultMax default value for allowed complexity.
078     */
079    protected AbstractClassCouplingCheck(int aDefaultMax)
080    {
081        setMax(aDefaultMax);
082    }
083
084    @Override
085    public final int[] getDefaultTokens()
086    {
087        return getRequiredTokens();
088    }
089
090    /** @return allowed complexity. */
091    public final int getMax()
092    {
093        return mMax;
094    }
095
096    /**
097     * Sets maximul allowed complexity.
098     * @param aMax allowed complexity.
099     */
100    public final void setMax(int aMax)
101    {
102        mMax = aMax;
103    }
104
105    /**
106     * Sets user-excluded classes to ignore.
107     * @param aExcludedClasses the list of classes to ignore.
108     */
109    public final void setExcludedClasses(String[] aExcludedClasses)
110    {
111        mExcludedClasses = ImmutableSet.copyOf(aExcludedClasses);
112    }
113
114    @Override
115    public final void beginTree(DetailAST aAST)
116    {
117        mPackageName = "";
118    }
119
120    /** @return message key we use for log violations. */
121    protected abstract String getLogMessageId();
122
123    @Override
124    public void visitToken(DetailAST aAST)
125    {
126        switch (aAST.getType()) {
127        case TokenTypes.PACKAGE_DEF:
128            visitPackageDef(aAST);
129            break;
130        case TokenTypes.CLASS_DEF:
131        case TokenTypes.INTERFACE_DEF:
132        case TokenTypes.ANNOTATION_DEF:
133        case TokenTypes.ENUM_DEF:
134            visitClassDef(aAST);
135            break;
136        case TokenTypes.TYPE:
137            mContext.visitType(aAST);
138            break;
139        case TokenTypes.LITERAL_NEW:
140            mContext.visitLiteralNew(aAST);
141            break;
142        case TokenTypes.LITERAL_THROWS:
143            mContext.visitLiteralThrows(aAST);
144            break;
145        default:
146            throw new IllegalStateException(aAST.toString());
147        }
148    }
149
150    @Override
151    public void leaveToken(DetailAST aAST)
152    {
153        switch (aAST.getType()) {
154        case TokenTypes.CLASS_DEF:
155        case TokenTypes.INTERFACE_DEF:
156        case TokenTypes.ANNOTATION_DEF:
157        case TokenTypes.ENUM_DEF:
158            leaveClassDef();
159            break;
160        default:
161            // Do nothing
162        }
163    }
164
165    /**
166     * Stores package of current class we check.
167     * @param aPkg package definition.
168     */
169    private void visitPackageDef(DetailAST aPkg)
170    {
171        final FullIdent ident = FullIdent.createFullIdent(aPkg.getLastChild()
172                .getPreviousSibling());
173        mPackageName = ident.getText();
174    }
175
176    /**
177     * Creates new context for a given class.
178     * @param aClassDef class definition node.
179     */
180    private void visitClassDef(DetailAST aClassDef)
181    {
182        mContextStack.push(mContext);
183        final String className =
184            aClassDef.findFirstToken(TokenTypes.IDENT).getText();
185        mContext = new Context(className,
186                               aClassDef.getLineNo(),
187                               aClassDef.getColumnNo());
188    }
189
190    /** Restores previous context. */
191    private void leaveClassDef()
192    {
193        mContext.checkCoupling();
194        mContext = mContextStack.pop();
195    }
196
197    /**
198     * Incapsulates information about class coupling.
199     *
200     * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
201     * @author o_sukhodolsky
202     */
203    private class Context
204    {
205        /**
206         * Set of referenced classes.
207         * Sorted by name for predictable error messages in unit tests.
208         */
209        private final Set<String> mReferencedClassNames = Sets.newTreeSet();
210        /** Own class name. */
211        private final String mClassName;
212        /* Location of own class. (Used to log violations) */
213        /** Line number of class definition. */
214        private final int mLineNo;
215        /** Column number of class definition. */
216        private final int mColumnNo;
217
218        /**
219         * Create new context associated with given class.
220         * @param aClassName name of the given class.
221         * @param aLineNo line of class definition.
222         * @param aColumnNo column of class definition.
223         */
224        public Context(String aClassName, int aLineNo, int aColumnNo)
225        {
226            mClassName = aClassName;
227            mLineNo = aLineNo;
228            mColumnNo = aColumnNo;
229        }
230
231        /**
232         * Visits throws clause and collects all exceptions we throw.
233         * @param aThrows throws to process.
234         */
235        public void visitLiteralThrows(DetailAST aThrows)
236        {
237            for (DetailAST childAST = aThrows.getFirstChild();
238                 childAST != null;
239                 childAST = childAST.getNextSibling())
240            {
241                if (childAST.getType() != TokenTypes.COMMA) {
242                    addReferencedClassName(childAST);
243                }
244            }
245        }
246
247        /**
248         * Visits type.
249         * @param aAST type to process.
250         */
251        public void visitType(DetailAST aAST)
252        {
253            final String className = CheckUtils.createFullType(aAST).getText();
254            mContext.addReferencedClassName(className);
255        }
256
257        /**
258         * Visits NEW.
259         * @param aAST NEW to process.
260         */
261        public void visitLiteralNew(DetailAST aAST)
262        {
263            mContext.addReferencedClassName(aAST.getFirstChild());
264        }
265
266        /**
267         * Adds new referenced class.
268         * @param aAST a node which represents referenced class.
269         */
270        private void addReferencedClassName(DetailAST aAST)
271        {
272            final String className = FullIdent.createFullIdent(aAST).getText();
273            addReferencedClassName(className);
274        }
275
276        /**
277         * Adds new referenced class.
278         * @param aClassName class name of the referenced class.
279         */
280        private void addReferencedClassName(String aClassName)
281        {
282            if (isSignificant(aClassName)) {
283                mReferencedClassNames.add(aClassName);
284            }
285        }
286
287        /** Checks if coupling less than allowed or not. */
288        public void checkCoupling()
289        {
290            mReferencedClassNames.remove(mClassName);
291            mReferencedClassNames.remove(mPackageName + "." + mClassName);
292
293            if (mReferencedClassNames.size() > mMax) {
294                log(mLineNo, mColumnNo, getLogMessageId(),
295                        mReferencedClassNames.size(), getMax(),
296                        mReferencedClassNames.toString());
297            }
298        }
299
300        /**
301         * Checks if given class shouldn't be ignored and not from java.lang.
302         * @param aClassName class to check.
303         * @return true if we should count this class.
304         */
305        private boolean isSignificant(String aClassName)
306        {
307            return (aClassName.length() > 0)
308                    && !mExcludedClasses.contains(aClassName)
309                    && !aClassName.startsWith("java.lang.");
310        }
311    }
312}