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}