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