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