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.puppycrawl.tools.checkstyle.api.Check;
022import com.puppycrawl.tools.checkstyle.api.DetailAST;
023import com.puppycrawl.tools.checkstyle.api.FastStack;
024import com.puppycrawl.tools.checkstyle.api.TokenTypes;
025import com.puppycrawl.tools.checkstyle.checks.CheckUtils;
026
027/**
028 * Restricts nested boolean operators (&&, ||, &, | and ^) to
029 * a specified depth (default = 3).
030 *
031 * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
032 * @author o_sukhodolsky
033 */
034public final class BooleanExpressionComplexityCheck extends Check
035{
036    /** Default allowed complexity. */
037    private static final int DEFAULT_MAX = 3;
038
039    /** Stack of contexts. */
040    private final FastStack<Context> contextStack = FastStack.newInstance();
041    /** Maximum allowed complexity. */
042    private int max;
043    /** Current context. */
044    private Context context;
045
046    /** Creates new instance of the check. */
047    public BooleanExpressionComplexityCheck()
048    {
049        setMax(DEFAULT_MAX);
050    }
051
052    @Override
053    public int[] getDefaultTokens()
054    {
055        return new int[] {
056            TokenTypes.CTOR_DEF,
057            TokenTypes.METHOD_DEF,
058            TokenTypes.EXPR,
059            TokenTypes.LAND,
060            TokenTypes.BAND,
061            TokenTypes.LOR,
062            TokenTypes.BOR,
063            TokenTypes.BXOR,
064        };
065    }
066
067    @Override
068    public int[] getRequiredTokens()
069    {
070        return new int[] {
071            TokenTypes.CTOR_DEF,
072            TokenTypes.METHOD_DEF,
073            TokenTypes.EXPR,
074        };
075    }
076
077    /**
078     * Getter for maximum allowed complexity.
079     * @return value of maximum allowed complexity.
080     */
081    public int getMax()
082    {
083        return max;
084    }
085
086    /**
087     * Setter for maximum allowed complexity.
088     * @param max new maximum allowed complexity.
089     */
090    public void setMax(int max)
091    {
092        this.max = max;
093    }
094
095    @Override
096    public void visitToken(DetailAST ast)
097    {
098        switch (ast.getType()) {
099            case TokenTypes.CTOR_DEF:
100            case TokenTypes.METHOD_DEF:
101                visitMethodDef(ast);
102                break;
103            case TokenTypes.EXPR:
104                visitExpr();
105                break;
106            case TokenTypes.LAND:
107            case TokenTypes.BAND:
108            case TokenTypes.LOR:
109            case TokenTypes.BOR:
110            case TokenTypes.BXOR:
111                context.visitBooleanOperator();
112                break;
113            default:
114                throw new IllegalStateException(ast.toString());
115        }
116    }
117
118    @Override
119    public void leaveToken(DetailAST ast)
120    {
121        switch (ast.getType()) {
122            case TokenTypes.CTOR_DEF:
123            case TokenTypes.METHOD_DEF:
124                leaveMethodDef();
125                break;
126            case TokenTypes.EXPR:
127                leaveExpr(ast);
128                break;
129            default:
130                // Do nothing
131        }
132    }
133
134    /**
135     * Creates new context for a given method.
136     * @param ast a method we start to check.
137     */
138    private void visitMethodDef(DetailAST ast)
139    {
140        contextStack.push(context);
141        context = new Context(!CheckUtils.isEqualsMethod(ast));
142    }
143
144    /** Removes old context. */
145    private void leaveMethodDef()
146    {
147        context = contextStack.pop();
148    }
149
150    /** Creates and pushes new context. */
151    private void visitExpr()
152    {
153        contextStack.push(context);
154        context = new Context((context == null) || context.isChecking());
155    }
156
157    /**
158     * Restores previous context.
159     * @param ast expression we leave.
160     */
161    private void leaveExpr(DetailAST ast)
162    {
163        context.checkCount(ast);
164        context = contextStack.pop();
165    }
166
167    /**
168     * Represents context (method/expression) in which we check complexity.
169     *
170     * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
171     * @author o_sukhodolsky
172     */
173    private class Context
174    {
175        /**
176         * Should we perform check in current context or not.
177         * Usually false if we are inside equals() method.
178         */
179        private final boolean checking;
180        /** Count of boolean operators. */
181        private int count;
182
183        /**
184         * Creates new instance.
185         * @param checking should we check in current context or not.
186         */
187        public Context(boolean checking)
188        {
189            this.checking = checking;
190            count = 0;
191        }
192
193        /**
194         * Getter for checking property.
195         * @return should we check in current context or not.
196         */
197        public boolean isChecking()
198        {
199            return checking;
200        }
201
202        /** Increases operator counter. */
203        public void visitBooleanOperator()
204        {
205            ++count;
206        }
207
208        /**
209         * Checks if we violates maximum allowed complexity.
210         * @param ast a node we check now.
211         */
212        public void checkCount(DetailAST ast)
213        {
214            if (checking && (count > getMax())) {
215                final DetailAST parentAST = ast.getParent();
216
217                log(parentAST.getLineNo(), parentAST.getColumnNo(),
218                    "booleanExpressionComplexity", count, getMax());
219            }
220        }
221    }
222}