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.indentation;
020
021import com.puppycrawl.tools.checkstyle.api.Check;
022import com.puppycrawl.tools.checkstyle.api.DetailAST;
023import com.puppycrawl.tools.checkstyle.api.FastStack;
024
025// TODO: allow preset indentation styles (IE... GNU style, Sun style, etc...)?
026
027// TODO: optionally make imports (and other?) statements required to start
028//   line? -- but maybe this should be a different check
029
030// TODO: optionally allow array children, throws clause, etc...
031//   to be of any indentation > required, for emacs-style indentation
032
033// TODO: this is not illegal, but probably should be:
034//        myfunc3(11, 11, Integer.
035//            getInteger("mytest").intValue(),  // this should be in 4 more
036//            11);
037
038// TODO: any dot-based indentation doesn't work (at least not yet...) the
039//  problem is that we don't know which way an expression tree will be built
040//  and with dot trees, they are built backwards.  This means code like
041//
042//  org.blah.mystuff
043//      .myclass.getFactoryObject()
044//          .objFunc().otherMethod();
045// and
046//  return ((MethodCallHandler) parent)
047//      .findContainingMethodCall(this);
048//  is all checked at the level of the first line.  Simple dots are actually
049// checked but the method call handler will have to be changed drastically
050// to fix the above...
051
052
053/**
054 * Checks correct indentation of Java Code.
055 *
056 * <p>
057 * The basic idea behind this is that while
058 * pretty printers are sometimes convenient for bulk reformats of
059 * legacy code, they often either aren't configurable enough or
060 * just can't anticipate how format should be done.  Sometimes this is
061 * personal preference, other times it is practical experience.  In any
062 * case, this check should just ensure that a minimal set of indentation
063 * rules are followed.
064 * </p>
065 *
066 * <p>
067 * Implementation --
068 *  Basically, this check requests visitation for all handled token
069 *  types (those tokens registered in the HandlerFactory).  When visitToken
070 *  is called, a new ExpressionHandler is created for the AST and pushed
071 *  onto the mHandlers stack.  The new handler then checks the indentation
072 *  for the currently visiting AST.  When leaveToken is called, the
073 *  ExpressionHandler is popped from the stack.
074 * </p>
075 *
076 * <p>
077 *  While on the stack the ExpressionHandler can be queried for the
078 *  indentation level it suggests for children as well as for other
079 *  values.
080 * </p>
081 *
082 * <p>
083 *  While an ExpressionHandler checks the indentation level of its own
084 *  AST, it typically also checks surrounding ASTs.  For instance, a
085 *  while loop handler checks the while loop as well as the braces
086 *  and immediate children.
087 * </p>
088 * <pre>
089 *   - handler class -to-&gt; ID mapping kept in Map
090 *   - parent passed in during construction
091 *   - suggest child indent level
092 *   - allows for some tokens to be on same line (ie inner classes OBJBLOCK)
093 *     and not increase indentation level
094 *   - looked at using double dispatch for suggestedChildLevel(), but it
095 *     doesn't seem worthwhile, at least now
096 *   - both tabs and spaces are considered whitespace in front of the line...
097 *     tabs are converted to spaces
098 *   - block parents with parens -- for, while, if, etc... -- are checked that
099 *     they match the level of the parent
100 * </pre>
101 *
102 * @author jrichard
103 * @author o_sukhodolsky
104 * @author Maikel Steneker
105 * @author maxvetrenko
106 */
107public class IndentationCheck extends Check
108{
109    /** Default indentation amount - based on Sun */
110    private static final int DEFAULT_INDENTATION = 4;
111
112    /** how many tabs or spaces to use */
113    private int mBasicOffset = DEFAULT_INDENTATION;
114
115    /** how much to indent a case label */
116    private int mCaseIndentationAmount = DEFAULT_INDENTATION;
117
118    /** how far brace should be indented when on next line */
119    private int mBraceAdjustment;
120
121    /** how far throws should be indented when on next line */
122    private int mThrowsIndentationAmount = DEFAULT_INDENTATION;
123
124    /** how much to indent an array initialization when on next line */
125    private int mArrayInitIndentationAmount = DEFAULT_INDENTATION;
126
127    /** how far continuation line should be indented when line-wrapping is present */
128    private int mLineWrappingIndentation = DEFAULT_INDENTATION;
129
130    /** handlers currently in use */
131    private final FastStack<ExpressionHandler> mHandlers =
132        FastStack.newInstance();
133
134    /** factory from which handlers are distributed */
135    private final HandlerFactory mHandlerFactory = new HandlerFactory();
136
137    /** Creates a new instance of IndentationCheck. */
138    public IndentationCheck()
139    {
140    }
141
142    /**
143     * Set the basic offset.
144     *
145     * @param aBasicOffset   the number of tabs or spaces to indent
146     */
147    public void setBasicOffset(int aBasicOffset)
148    {
149        mBasicOffset = aBasicOffset;
150    }
151
152    /**
153     * Get the basic offset.
154     *
155     * @return the number of tabs or spaces to indent
156     */
157    public int getBasicOffset()
158    {
159        return mBasicOffset;
160    }
161
162    /**
163     * Adjusts brace indentation (positive offset).
164     *
165     * @param aAdjustmentAmount   the brace offset
166     */
167    public void setBraceAdjustment(int aAdjustmentAmount)
168    {
169        mBraceAdjustment = aAdjustmentAmount;
170    }
171
172    /**
173     * Get the brace adjustment amount.
174     *
175     * @return the positive offset to adjust braces
176     */
177    public int getBraceAdjustement()
178    {
179        return mBraceAdjustment;
180    }
181
182    /**
183     * Set the case indentation level.
184     *
185     * @param aAmount   the case indentation level
186     */
187    public void setCaseIndent(int aAmount)
188    {
189        mCaseIndentationAmount = aAmount;
190    }
191
192    /**
193     * Get the case indentation level.
194     *
195     * @return the case indentation level
196     */
197    public int getCaseIndent()
198    {
199        return mCaseIndentationAmount;
200    }
201
202    /**
203     * Set the throws indentation level.
204     *
205     * @param aThrowsIndent the throws indentation level
206     */
207    public void setThrowsIndent(int aThrowsIndent)
208    {
209        mThrowsIndentationAmount = aThrowsIndent;
210    }
211
212    /**
213     * Get the throws indentation level.
214     *
215     * @return the throws indentation level
216     */
217    public int getThrowsIndent()
218    {
219        return this.mThrowsIndentationAmount;
220    }
221
222    /**
223     * Set the array initialisation indentation level.
224     *
225     * @param aArrayInitIndent the array initialisation indentation level
226     */
227    public void setArrayInitIndent(int aArrayInitIndent)
228    {
229        mArrayInitIndentationAmount = aArrayInitIndent;
230    }
231
232    /**
233     * Get the line-wrapping indentation level.
234     *
235     * @return the initialisation indentation level
236     */
237    public int getArrayInitIndent()
238    {
239        return this.mArrayInitIndentationAmount;
240    }
241
242    /**
243     * Get the array line-wrapping indentation level.
244     *
245     * @return the line-wrapping indentation level
246     */
247    public int getLineWrappingIndentation()
248    {
249        return mLineWrappingIndentation;
250    }
251
252
253    /**
254     * Set the line-wrapping indentation level.
255     *
256     * @param aLineWrappingIndentation the line-wrapping indentation level
257     */
258    public void setLineWrappingIndentation(int aLineWrappingIndentation)
259    {
260        mLineWrappingIndentation = aLineWrappingIndentation;
261    }
262
263    /**
264     * Log an error message.
265     *
266     * @param aLine the line number where the error was found
267     * @param aKey the message that describes the error
268     * @param aArgs the details of the message
269     *
270     * @see java.text.MessageFormat
271     */
272    public void indentationLog(int aLine, String aKey, Object... aArgs)
273    {
274        super.log(aLine, aKey, aArgs);
275    }
276
277    /**
278     * Get the width of a tab.
279     *
280     * @return the width of a tab
281     */
282    public int getIndentationTabWidth()
283    {
284        return getTabWidth();
285    }
286
287    @Override
288    public int[] getDefaultTokens()
289    {
290        return mHandlerFactory.getHandledTypes();
291    }
292
293    @Override
294    public void beginTree(DetailAST aAst)
295    {
296        mHandlerFactory.clearCreatedHandlers();
297        mHandlers.clear();
298        mHandlers.push(new PrimordialHandler(this));
299    }
300
301    @Override
302    public void visitToken(DetailAST aAST)
303    {
304        final ExpressionHandler handler = mHandlerFactory.getHandler(this, aAST,
305            mHandlers.peek());
306        mHandlers.push(handler);
307        try {
308            handler.checkIndentation();
309        }
310        catch (final NullPointerException npe) {
311            npe.printStackTrace();
312        }
313    }
314
315    @Override
316    public void leaveToken(DetailAST aAST)
317    {
318        mHandlers.pop();
319    }
320
321    /**
322     * Accessor for the handler factory.
323     *
324     * @return the handler factory
325     */
326    final HandlerFactory getHandlerFactory()
327    {
328        return mHandlerFactory;
329    }
330}