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> mContextStack = FastStack.newInstance();
041    /** Maximum allowed complexity. */
042    private int mMax;
043    /** Current context. */
044    private Context mContext;
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 mMax;
084    }
085
086    /**
087     * Setter for maximum allowed complexity.
088     * @param aMax new maximum allowed complexity.
089     */
090    public void setMax(int aMax)
091    {
092        mMax = aMax;
093    }
094
095    @Override
096    public void visitToken(DetailAST aAST)
097    {
098        switch (aAST.getType()) {
099        case TokenTypes.CTOR_DEF:
100        case TokenTypes.METHOD_DEF:
101            visitMethodDef(aAST);
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            mContext.visitBooleanOperator();
112            break;
113        default:
114            throw new IllegalStateException(aAST.toString());
115        }
116    }
117
118    @Override
119    public void leaveToken(DetailAST aAST)
120    {
121        switch (aAST.getType()) {
122        case TokenTypes.CTOR_DEF:
123        case TokenTypes.METHOD_DEF:
124            leaveMethodDef();
125            break;
126        case TokenTypes.EXPR:
127            leaveExpr(aAST);
128            break;
129        default:
130            // Do nothing
131        }
132    }
133
134    /**
135     * Creates new context for a given method.
136     * @param aAST a method we start to check.
137     */
138    private void visitMethodDef(DetailAST aAST)
139    {
140        mContextStack.push(mContext);
141        mContext = new Context(!CheckUtils.isEqualsMethod(aAST));
142    }
143
144    /** Removes old context. */
145    private void leaveMethodDef()
146    {
147        mContext = mContextStack.pop();
148    }
149
150    /** Creates and pushes new context. */
151    private void visitExpr()
152    {
153        mContextStack.push(mContext);
154        mContext = new Context((mContext == null) || mContext.isChecking());
155    }
156
157    /**
158     * Restores previous context.
159     * @param aAST expression we leave.
160     */
161    private void leaveExpr(DetailAST aAST)
162    {
163        mContext.checkCount(aAST);
164        mContext = mContextStack.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 mChecking;
180        /** Count of boolean operators. */
181        private int mCount;
182
183        /**
184         * Creates new instance.
185         * @param aChecking should we check in current context or not.
186         */
187        public Context(boolean aChecking)
188        {
189            mChecking = aChecking;
190            mCount = 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 mChecking;
200        }
201
202        /** Increases operator counter. */
203        public void visitBooleanOperator()
204        {
205            ++mCount;
206        }
207
208        /**
209         * Checks if we violates maximum allowed complexity.
210         * @param aAST a node we check now.
211         */
212        public void checkCount(DetailAST aAST)
213        {
214            if (mChecking && (mCount > getMax())) {
215                final DetailAST parentAST = aAST.getParent();
216
217                log(parentAST.getLineNo(), parentAST.getColumnNo(),
218                    "booleanExpressionComplexity", mCount, getMax());
219            }
220        }
221    }
222}