View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2015 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  package com.puppycrawl.tools.checkstyle.checks.indentation;
20  
21  import java.util.Collection;
22  import java.util.Iterator;
23  import java.util.NavigableMap;
24  import java.util.TreeMap;
25  
26  import com.puppycrawl.tools.checkstyle.api.DetailAST;
27  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
28  
29  /**
30   * This class checks line-wrapping into definitions and expressions. The
31   * line-wrapping indentation should be not less then value of the
32   * lineWrappingIndentation parameter.
33   *
34   * @author maxvetrenko
35   *
36   */
37  public class LineWrappingHandler
38  {
39  
40      /**
41       * The current instance of <code>IndentationCheck</code> class using this
42       * handler. This field used to get access to private fields of
43       * IndentationCheck instance.
44       */
45      private final IndentationCheck indentCheck;
46  
47      /**
48       * Root node for current expression.
49       */
50      private DetailAST firstNode;
51  
52      /**
53       * Last node for current expression.
54       */
55      private DetailAST lastNode;
56  
57      /**
58       * User's value of line wrapping indentation.
59       */
60      private int indentLevel;
61  
62      /**
63       * Force strict condition in line wrapping case.
64       */
65      private boolean forceStrictCondition;
66  
67      /**
68       * Sets values of class field, finds last node and calculates indentation level.
69       *
70       * @param instance
71       *            instance of IndentationCheck.
72       * @param firstNode
73       *            root node for current expression.
74       * @param lastNode
75       *            last node for current expression.
76       */
77      public LineWrappingHandler(IndentationCheck instance, DetailAST firstNode, DetailAST lastNode)
78      {
79          indentCheck = instance;
80          this.firstNode = firstNode;
81          this.lastNode = lastNode;
82          indentLevel = indentCheck.getLineWrappingIndentation();
83          forceStrictCondition = indentCheck.getForceStrictCondition();
84      }
85  
86      /**
87       * @return correct indentation for current expression.
88       */
89      protected int getCurrentIndentation()
90      {
91          return firstNode.getColumnNo() + indentLevel;
92      }
93  
94      // Getters for private fields.
95  
96      protected final DetailAST getFirstNode()
97      {
98          return firstNode;
99      }
100 
101     protected final DetailAST getLastNode()
102     {
103         return lastNode;
104     }
105 
106     protected final int getIndentLevel()
107     {
108         return indentLevel;
109     }
110 
111     /**
112      * Checks line wrapping into expressions and definitions.
113      */
114     public void checkIndentation()
115     {
116         final NavigableMap<Integer, DetailAST> firstNodesOnLines = collectFirstNodes();
117 
118         final DetailAST firstNode = firstNodesOnLines.get(firstNodesOnLines.firstKey());
119         if (firstNode.getType() == TokenTypes.AT) {
120             checkAnnotationIndentation(firstNode, firstNodesOnLines);
121         }
122 
123         // First node should be removed because it was already checked before.
124         firstNodesOnLines.remove(firstNodesOnLines.firstKey());
125         final int firstNodeIndent = getFirstNodeIndent(firstNode);
126         final int currentIndent = firstNodeIndent + indentLevel;
127 
128         for (DetailAST node : firstNodesOnLines.values()) {
129             final int currentType = node.getType();
130 
131             if (currentType == TokenTypes.RCURLY
132                     || currentType == TokenTypes.RPAREN
133                     || currentType == TokenTypes.ARRAY_INIT)
134             {
135                 logWarningMessage(node, firstNodeIndent);
136             }
137             else if (currentType == TokenTypes.LITERAL_IF) {
138                 final DetailAST parent = node.getParent();
139 
140                 if (parent.getType() == TokenTypes.LITERAL_ELSE) {
141                     logWarningMessage(parent, currentIndent);
142                 }
143             }
144             else {
145                 logWarningMessage(node, currentIndent);
146             }
147         }
148     }
149 
150     /**
151      * Calculates indentation of first node.
152      *
153      * @param node
154      *            first node.
155      * @return indentation of first node.
156      */
157     private int getFirstNodeIndent(DetailAST node)
158     {
159         int indentLevel = node.getColumnNo();
160 
161         if (node.getType() == TokenTypes.LITERAL_IF
162                 && node.getParent().getType() == TokenTypes.LITERAL_ELSE)
163         {
164             final DetailAST lcurly = node.getParent().getPreviousSibling();
165             final DetailAST rcurly = lcurly.getLastChild();
166 
167             if (lcurly.getType() == TokenTypes.SLIST
168                     && rcurly.getLineNo() == node.getLineNo())
169             {
170                 indentLevel = rcurly.getColumnNo();
171             }
172             else {
173                 indentLevel = node.getParent().getColumnNo();
174             }
175         }
176         return indentLevel;
177     }
178 
179     /**
180      * Finds first nodes on line and puts them into Map.
181      *
182      * @return NavigableMap which contains lines numbers as a key and first
183      *         nodes on lines as a values.
184      */
185     private NavigableMap<Integer, DetailAST> collectFirstNodes()
186     {
187         final NavigableMap<Integer, DetailAST> result = new TreeMap<>();
188 
189         result.put(firstNode.getLineNo(), firstNode);
190         DetailAST curNode = firstNode.getFirstChild();
191 
192         while (curNode != null && curNode != lastNode) {
193 
194             if (curNode.getType() == TokenTypes.OBJBLOCK) {
195                 curNode = curNode.getNextSibling();
196             }
197 
198             if (curNode != null) {
199                 final DetailAST firstTokenOnLine = result.get(curNode.getLineNo());
200 
201                 if (firstTokenOnLine == null
202                         || firstTokenOnLine != null
203                         && firstTokenOnLine.getColumnNo() >= curNode.getColumnNo())
204                 {
205                     result.put(curNode.getLineNo(), curNode);
206                 }
207                 curNode = getNextCurNode(curNode);
208             }
209         }
210         return result;
211     }
212 
213     /**
214      * Returns next curNode node.
215      *
216      * @param curNode current node.
217      * @return next curNode node.
218      */
219     private DetailAST getNextCurNode(DetailAST curNode)
220     {
221         DetailAST nodeToVisit = curNode.getFirstChild();
222         DetailAST currentNode = curNode;
223 
224         while (currentNode != null && nodeToVisit == null) {
225             nodeToVisit = currentNode.getNextSibling();
226             if (nodeToVisit == null) {
227                 currentNode = currentNode.getParent();
228             }
229         }
230         return nodeToVisit;
231     }
232 
233     /**
234      * Checks line wrapping into annotations.
235      *
236      * @param atNode at-clause node.
237      * @param firstNodesOnLines map which contains
238      *     first nodes as values and line numbers as keys.
239      */
240     private void checkAnnotationIndentation(DetailAST atNode,
241             NavigableMap<Integer, DetailAST> firstNodesOnLines)
242     {
243         final int currentIndent = atNode.getColumnNo() + indentLevel;
244         final int firstNodeIndent = atNode.getColumnNo();
245         final Collection<DetailAST> values = firstNodesOnLines.values();
246         final DetailAST lastAnnotationNode = getLastAnnotationNode(atNode);
247         final int lastAnnotationLine = lastAnnotationNode.getLineNo();
248         final int lastAnnotattionColumn = lastAnnotationNode.getColumnNo();
249 
250         final Iterator<DetailAST> itr = values.iterator();
251         while (itr.hasNext() && firstNodesOnLines.size() > 1) {
252             final DetailAST node = itr.next();
253 
254             if (node.getLineNo() < lastAnnotationLine
255                     || node.getLineNo() == lastAnnotationLine
256                     && node.getColumnNo() <= lastAnnotattionColumn)
257             {
258                 final DetailAST parentNode = node.getParent();
259                 if (node.getType() == TokenTypes.AT
260                         && parentNode.getParent().getType() == TokenTypes.MODIFIERS)
261                 {
262                     logWarningMessage(node, firstNodeIndent);
263                 }
264                 else {
265                     logWarningMessage(node, currentIndent);
266                 }
267                 itr.remove();
268             }
269             else {
270                 break;
271             }
272         }
273     }
274 
275     /**
276      * Finds and returns last annotation node.
277      * @param atNode first at-clause node.
278      * @return last annotation node.
279      */
280     private DetailAST getLastAnnotationNode(DetailAST atNode)
281     {
282         DetailAST lastAnnotation = atNode.getParent();
283         while (lastAnnotation.getNextSibling() != null
284                 && lastAnnotation.getNextSibling().getType() == TokenTypes.ANNOTATION)
285         {
286             lastAnnotation = lastAnnotation.getNextSibling();
287         }
288         return lastAnnotation.getLastChild();
289     }
290 
291     /**
292      * Logs warning message if indentation is incorrect.
293      *
294      * @param currentNode
295      *            current node which probably invoked an error.
296      * @param currentIndent
297      *            correct indentation.
298      */
299     private void logWarningMessage(DetailAST currentNode, int currentIndent)
300     {
301         if (forceStrictCondition) {
302             if (currentNode.getColumnNo() != currentIndent) {
303                 indentCheck.indentationLog(currentNode.getLineNo(),
304                         "indentation.error", currentNode.getText(),
305                         currentNode.getColumnNo(), currentIndent);
306             }
307         }
308         else {
309             if (currentNode.getColumnNo() < currentIndent) {
310                 indentCheck.indentationLog(currentNode.getLineNo(),
311                         "indentation.error", currentNode.getText(),
312                         currentNode.getColumnNo(), currentIndent);
313             }
314         }
315     }
316 }