View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2014  Oliver Burn
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       */
75      public LineWrappingHandler(IndentationCheck instance, DetailAST firstNode)
76      {
77          indentCheck = instance;
78          this.firstNode = firstNode;
79          lastNode = findLastNode(firstNode);
80          indentLevel = indentCheck.getLineWrappingIndentation();
81          forceStrictCondition = indentCheck.getForceStrictCondition();
82      }
83  
84      /**
85       * Finds last node of AST subtree.
86       *
87       * @param firstNode the first node of expression or definition.
88       * @return last node.
89       */
90      public DetailAST findLastNode(DetailAST firstNode)
91      {
92          return firstNode.getLastChild().getPreviousSibling();
93      }
94  
95      /**
96       * @return correct indentation for current expression.
97       */
98      public int getCurrentIndentation()
99      {
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 }