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 java.util.Collection;
022import java.util.Iterator;
023import java.util.NavigableMap;
024import java.util.TreeMap;
025
026import com.puppycrawl.tools.checkstyle.api.DetailAST;
027import com.puppycrawl.tools.checkstyle.api.TokenTypes;
028
029/**
030 * This class checks line-wrapping into definitions and expressions. The
031 * line-wrapping indentation should be not less then value of the
032 * lineWrappingIndentation parameter.
033 *
034 * @author maxvetrenko
035 *
036 */
037public class LineWrappingHandler
038{
039
040    /**
041     * The current instance of <code>IndentationCheck</code> class using this
042     * handler. This field used to get access to private fields of
043     * IndentationCheck instance.
044     */
045    private final IndentationCheck mIndentCheck;
046
047    /**
048     * Root node for current expression.
049     */
050    private DetailAST mFirstNode;
051
052    /**
053     * Last node for current expression.
054     */
055    private DetailAST mLastNode;
056
057    /**
058     * User's value of line wrapping indentation.
059     */
060    private int mIndentLevel;
061
062    /**
063     * Sets values of class field, finds last node and calculates indentation level.
064     *
065     * @param aInstance
066     *            instance of IndentationCheck.
067     * @param aFirstNode
068     *            root node for current expression..
069     */
070    public LineWrappingHandler(IndentationCheck aInstance, DetailAST aFirstNode)
071    {
072        mIndentCheck = aInstance;
073        mFirstNode = aFirstNode;
074        mLastNode = findLastNode(mFirstNode);
075        mIndentLevel = mIndentCheck.getLineWrappingIndentation();
076    }
077
078    /**
079     * Finds last node of AST subtree.
080     *
081     * @param aFirstNode the first node of expression or definition.
082     * @return last node.
083     */
084    public DetailAST findLastNode(DetailAST aFirstNode)
085    {
086        return aFirstNode.getLastChild().getPreviousSibling();
087    }
088
089    /**
090     * @return correct indentation for current expression.
091     */
092    public int getCurrentIndentation()
093    {
094        return mFirstNode.getColumnNo() + mIndentLevel;
095    }
096
097    // Getters for private fields.
098
099    public final DetailAST getFirstNode()
100    {
101        return mFirstNode;
102    }
103
104    public final DetailAST getLastNode()
105    {
106        return mLastNode;
107    }
108
109    public final int getIndentLevel()
110    {
111        return mIndentLevel;
112    }
113
114    /**
115     * Checks line wrapping into expressions and definitions.
116     */
117    public void checkIndentation()
118    {
119        final NavigableMap<Integer, DetailAST> firstNodesOnLines = collectFirstNodes();
120
121        final DetailAST firstNode = firstNodesOnLines.get(firstNodesOnLines.firstKey());
122        if (firstNode.getType() == TokenTypes.AT) {
123            checkAnnotationIndentation(firstNode, firstNodesOnLines);
124        }
125
126        // First node should be removed because it was already checked before.
127        firstNodesOnLines.remove(firstNodesOnLines.firstKey());
128        final int firstNodeIndent = getFirstNodeIndent(firstNode);
129        final int currentIndent = firstNodeIndent + mIndentLevel;
130
131        for (DetailAST node : firstNodesOnLines.values()) {
132            final int currentType = node.getType();
133
134            if (currentType == TokenTypes.RCURLY
135                    || currentType == TokenTypes.RPAREN
136                    || currentType == TokenTypes.ARRAY_INIT)
137            {
138                logWarningMessage(node, firstNodeIndent);
139            }
140            else if (currentType == TokenTypes.LITERAL_IF) {
141                final DetailAST parent = node.getParent();
142
143                if (parent.getType() == TokenTypes.LITERAL_ELSE) {
144                    logWarningMessage(parent, currentIndent);
145                }
146            }
147            else {
148                logWarningMessage(node, currentIndent);
149            }
150        }
151    }
152
153    /**
154     * Calculates indentation of first node.
155     *
156     * @param aNode
157     *            first node.
158     * @return indentation of first node.
159     */
160    private int getFirstNodeIndent(DetailAST aNode)
161    {
162        int indentLevel = aNode.getColumnNo();
163
164        if (aNode.getType() == TokenTypes.LITERAL_IF
165                && aNode.getParent().getType() == TokenTypes.LITERAL_ELSE)
166        {
167            final DetailAST lcurly = aNode.getParent().getPreviousSibling();
168            final DetailAST rcurly = lcurly.getLastChild();
169
170            if (lcurly.getType() == TokenTypes.SLIST
171                    && rcurly.getLineNo() == aNode.getLineNo())
172            {
173                indentLevel = rcurly.getColumnNo();
174            }
175            else {
176                indentLevel = aNode.getParent().getColumnNo();
177            }
178        }
179        return indentLevel;
180    }
181
182    /**
183     * Finds first nodes on line and puts them into Map.
184     *
185     * @return NavigableMap which contains lines numbers as a key and first
186     *         nodes on lines as a values.
187     */
188    private NavigableMap<Integer, DetailAST> collectFirstNodes()
189    {
190        final NavigableMap<Integer, DetailAST> result = new TreeMap<Integer, DetailAST>();
191
192        result.put(mFirstNode.getLineNo(), mFirstNode);
193        DetailAST curNode = mFirstNode.getFirstChild();
194
195        while (curNode != null && curNode != mLastNode) {
196
197            if (curNode.getType() == TokenTypes.OBJBLOCK) {
198                curNode = curNode.getNextSibling();
199            }
200
201            if (curNode != null) {
202                final DetailAST firstTokenOnLine = result.get(curNode.getLineNo());
203
204                if (firstTokenOnLine == null
205                        || firstTokenOnLine != null
206                        && firstTokenOnLine.getColumnNo() >= curNode.getColumnNo())
207                {
208                    result.put(curNode.getLineNo(), curNode);
209                }
210                curNode = getNextCurNode(curNode);
211            }
212        }
213        return result;
214    }
215
216    /**
217     * Returns next curNode node.
218     *
219     * @param aCurNode current node.
220     * @return next curNode node.
221     */
222    private DetailAST getNextCurNode(DetailAST aCurNode)
223    {
224        DetailAST nodeToVisit = aCurNode.getFirstChild();
225        DetailAST currentNode = aCurNode;
226
227        while ((currentNode != null) && (nodeToVisit == null)) {
228            nodeToVisit = currentNode.getNextSibling();
229            if (nodeToVisit == null) {
230                currentNode = currentNode.getParent();
231            }
232        }
233        return nodeToVisit;
234    }
235
236    /**
237     * Checks line wrapping into annotations.
238     *
239     * @param aModifiersNode modifiers node.
240     * @param aFirstNodesOnLines map which contains
241     *     first nodes as values and line numbers as keys.
242     */
243    private void checkAnnotationIndentation(DetailAST aModifiersNode,
244            NavigableMap<Integer, DetailAST> aFirstNodesOnLines)
245    {
246        final int currentIndent = aModifiersNode.getColumnNo() + mIndentLevel;
247        final int firstNodeIndent = aModifiersNode.getColumnNo();
248        final Collection<DetailAST> values = aFirstNodesOnLines.values();
249
250        final Iterator<DetailAST> itr = values.iterator();
251        while (itr.hasNext() && aFirstNodesOnLines.size() > 1) {
252            final DetailAST node = itr.next();
253            final int parentType = node.getParent().getType();
254
255            if (node.getType() == TokenTypes.AT) {
256
257                if (isAnnotationAloneOnLine(node.getParent())) {
258                    logWarningMessage(node, firstNodeIndent);
259                    itr.remove();
260                }
261            }
262            else if (parentType != TokenTypes.MODIFIERS
263                    && !hasTypeNodeAsParent(node)
264                    && parentType != TokenTypes.ENUM_DEF
265                    && parentType != TokenTypes.CTOR_DEF
266                    && node.getType() != TokenTypes.LITERAL_CLASS)
267            {
268                logWarningMessage(node, currentIndent);
269                itr.remove();
270            }
271        }
272    }
273
274    /**
275     * Checks if annotation is alone on line.
276     *
277     * @param aAnnotationNode
278     *            current annotation.
279     * @return true if annotation is alone on line.
280     */
281    private boolean isAnnotationAloneOnLine(DetailAST aAnnotationNode)
282    {
283        final DetailAST nextSibling = aAnnotationNode.getNextSibling();
284        if (nextSibling == null) {
285            final DetailAST typeNode = aAnnotationNode.getParent().getNextSibling();
286            return aAnnotationNode.getLineNo() != typeNode.getLineNo();
287        }
288        else {
289            return (nextSibling.getType() == TokenTypes.ANNOTATION
290                || aAnnotationNode.getLineNo() != nextSibling.getLineNo());
291        }
292    }
293
294    /**
295     * Checks if current node has TYPE node as a parent.
296     *
297     * @param aCurrentNode
298     *            current node.
299     * @return true if current node has TYPE node as a parent.
300     */
301    private boolean hasTypeNodeAsParent(DetailAST aCurrentNode)
302    {
303        DetailAST typeNode = aCurrentNode;
304        boolean result = false;
305        while (typeNode != null && typeNode.getType() != TokenTypes.SLIST
306                && typeNode.getType() != TokenTypes.OBJBLOCK)
307        {
308            if (typeNode.getType() == TokenTypes.TYPE
309                    || typeNode.getType() == TokenTypes.TYPE_PARAMETERS)
310            {
311                result = true;
312            }
313            typeNode = typeNode.getParent();
314        }
315        return result;
316    }
317
318    /**
319     * Logs warning message if indentation is incorrect.
320     *
321     * @param aCurrentNode
322     *            current node which probably invoked an error.
323     * @param aCurrentIndent
324     *            correct indentation.
325     */
326    private void logWarningMessage(DetailAST aCurrentNode, int aCurrentIndent)
327    {
328        if (aCurrentNode.getColumnNo() < aCurrentIndent) {
329            mIndentCheck.indentationLog(aCurrentNode.getLineNo(),
330                    "indentation.error", aCurrentNode.getText(),
331                    aCurrentNode.getColumnNo(), aCurrentIndent);
332        }
333    }
334}