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}