001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2015 the original author or authors. 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//////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.indentation; 021 022import java.util.Collection; 023import java.util.Iterator; 024import java.util.NavigableMap; 025import java.util.TreeMap; 026 027import com.puppycrawl.tools.checkstyle.api.DetailAST; 028import com.puppycrawl.tools.checkstyle.api.TokenTypes; 029 030/** 031 * This class checks line-wrapping into definitions and expressions. The 032 * line-wrapping indentation should be not less then value of the 033 * lineWrappingIndentation parameter. 034 * 035 * @author maxvetrenko 036 * 037 */ 038public class LineWrappingHandler { 039 040 /** 041 * A key is pointing to the warning message text in "messages.properties" 042 * file. 043 */ 044 private static final String MSG_INDENTATION_ERROR = "indentation.error"; 045 046 /** 047 * The current instance of {@code IndentationCheck} class using this 048 * handler. This field used to get access to private fields of 049 * IndentationCheck instance. 050 */ 051 private final IndentationCheck indentCheck; 052 053 /** 054 * Root node for current expression. 055 */ 056 private final DetailAST firstNode; 057 058 /** 059 * Last node for current expression. 060 */ 061 private final DetailAST lastNode; 062 063 /** 064 * User's value of line wrapping indentation. 065 */ 066 private final int indentLevel; 067 068 /** 069 * Force strict condition in line wrapping case. 070 */ 071 private final boolean forceStrictCondition; 072 073 /** 074 * Sets values of class field, finds last node and calculates indentation level. 075 * 076 * @param instance 077 * instance of IndentationCheck. 078 * @param firstNode 079 * root node for current expression. 080 * @param lastNode 081 * last node for current expression. 082 */ 083 public LineWrappingHandler(IndentationCheck instance, DetailAST firstNode, DetailAST lastNode) { 084 indentCheck = instance; 085 this.firstNode = firstNode; 086 this.lastNode = lastNode; 087 indentLevel = indentCheck.getLineWrappingIndentation(); 088 forceStrictCondition = indentCheck.isForceStrictCondition(); 089 } 090 091 /** 092 * Getter for lastNode field. 093 * @return lastNode field 094 */ 095 protected final DetailAST getLastNode() { 096 return lastNode; 097 } 098 099 /** 100 * Checks line wrapping into expressions and definitions. 101 */ 102 public void checkIndentation() { 103 final NavigableMap<Integer, DetailAST> firstNodesOnLines = collectFirstNodes(); 104 105 final DetailAST firstLineNode = firstNodesOnLines.get(firstNodesOnLines.firstKey()); 106 if (firstLineNode.getType() == TokenTypes.AT) { 107 checkAnnotationIndentation(firstLineNode, firstNodesOnLines); 108 } 109 110 // First node should be removed because it was already checked before. 111 firstNodesOnLines.remove(firstNodesOnLines.firstKey()); 112 final int firstNodeIndent = getFirstNodeIndent(firstLineNode); 113 final int currentIndent = firstNodeIndent + indentLevel; 114 115 for (DetailAST node : firstNodesOnLines.values()) { 116 final int currentType = node.getType(); 117 118 if (currentType == TokenTypes.RCURLY 119 || currentType == TokenTypes.RPAREN 120 || currentType == TokenTypes.ARRAY_INIT) { 121 logWarningMessage(node, firstNodeIndent); 122 } 123 else { 124 logWarningMessage(node, currentIndent); 125 } 126 } 127 } 128 129 /** 130 * Calculates indentation of first node. 131 * 132 * @param node 133 * first node. 134 * @return indentation of first node. 135 */ 136 private static int getFirstNodeIndent(DetailAST node) { 137 int indentLevel = node.getColumnNo(); 138 139 if (node.getType() == TokenTypes.LITERAL_IF 140 && node.getParent().getType() == TokenTypes.LITERAL_ELSE) { 141 final DetailAST lcurly = node.getParent().getPreviousSibling(); 142 final DetailAST rcurly = lcurly.getLastChild(); 143 144 if (lcurly.getType() == TokenTypes.SLIST 145 && rcurly.getLineNo() == node.getLineNo()) { 146 indentLevel = rcurly.getColumnNo(); 147 } 148 else { 149 indentLevel = node.getParent().getColumnNo(); 150 } 151 } 152 return indentLevel; 153 } 154 155 /** 156 * Finds first nodes on line and puts them into Map. 157 * 158 * @return NavigableMap which contains lines numbers as a key and first 159 * nodes on lines as a values. 160 */ 161 private NavigableMap<Integer, DetailAST> collectFirstNodes() { 162 final NavigableMap<Integer, DetailAST> result = new TreeMap<>(); 163 164 result.put(firstNode.getLineNo(), firstNode); 165 DetailAST curNode = firstNode.getFirstChild(); 166 167 while (curNode != null && curNode != lastNode) { 168 169 if (curNode.getType() == TokenTypes.OBJBLOCK 170 || curNode.getType() == TokenTypes.SLIST) { 171 curNode = curNode.getNextSibling(); 172 } 173 174 if (curNode != null) { 175 final DetailAST firstTokenOnLine = result.get(curNode.getLineNo()); 176 177 if (firstTokenOnLine == null 178 || firstTokenOnLine.getColumnNo() >= curNode.getColumnNo()) { 179 result.put(curNode.getLineNo(), curNode); 180 } 181 curNode = getNextCurNode(curNode); 182 } 183 } 184 return result; 185 } 186 187 /** 188 * Returns next curNode node. 189 * 190 * @param curNode current node. 191 * @return next curNode node. 192 */ 193 private static DetailAST getNextCurNode(DetailAST curNode) { 194 DetailAST nodeToVisit = curNode.getFirstChild(); 195 DetailAST currentNode = curNode; 196 197 while (nodeToVisit == null) { 198 nodeToVisit = currentNode.getNextSibling(); 199 if (nodeToVisit == null) { 200 currentNode = currentNode.getParent(); 201 } 202 } 203 return nodeToVisit; 204 } 205 206 /** 207 * Checks line wrapping into annotations. 208 * 209 * @param atNode at-clause node. 210 * @param firstNodesOnLines map which contains 211 * first nodes as values and line numbers as keys. 212 */ 213 private void checkAnnotationIndentation(DetailAST atNode, 214 NavigableMap<Integer, DetailAST> firstNodesOnLines) { 215 final int currentIndent = atNode.getColumnNo() + indentLevel; 216 final int firstNodeIndent = atNode.getColumnNo(); 217 final Collection<DetailAST> values = firstNodesOnLines.values(); 218 final DetailAST lastAnnotationNode = getLastAnnotationNode(atNode); 219 final int lastAnnotationLine = lastAnnotationNode.getLineNo(); 220 221 final Iterator<DetailAST> itr = values.iterator(); 222 while (firstNodesOnLines.size() > 1) { 223 final DetailAST node = itr.next(); 224 225 if (node.getLineNo() < lastAnnotationLine 226 || node.getLineNo() == lastAnnotationLine) { 227 final DetailAST parentNode = node.getParent(); 228 if (node.getType() == TokenTypes.AT 229 && parentNode.getParent().getType() == TokenTypes.MODIFIERS) { 230 logWarningMessage(node, firstNodeIndent); 231 } 232 else { 233 logWarningMessage(node, currentIndent); 234 } 235 itr.remove(); 236 } 237 else { 238 break; 239 } 240 } 241 } 242 243 /** 244 * Finds and returns last annotation node. 245 * @param atNode first at-clause node. 246 * @return last annotation node. 247 */ 248 private static DetailAST getLastAnnotationNode(DetailAST atNode) { 249 DetailAST lastAnnotation = atNode.getParent(); 250 while (lastAnnotation.getNextSibling() != null 251 && lastAnnotation.getNextSibling().getType() == TokenTypes.ANNOTATION) { 252 lastAnnotation = lastAnnotation.getNextSibling(); 253 } 254 return lastAnnotation.getLastChild(); 255 } 256 257 /** 258 * Logs warning message if indentation is incorrect. 259 * 260 * @param currentNode 261 * current node which probably invoked an error. 262 * @param currentIndent 263 * correct indentation. 264 */ 265 private void logWarningMessage(DetailAST currentNode, int currentIndent) { 266 if (forceStrictCondition) { 267 if (currentNode.getColumnNo() != currentIndent) { 268 indentCheck.indentationLog(currentNode.getLineNo(), 269 MSG_INDENTATION_ERROR, currentNode.getText(), 270 currentNode.getColumnNo(), currentIndent); 271 } 272 } 273 else { 274 if (currentNode.getColumnNo() < currentIndent) { 275 indentCheck.indentationLog(currentNode.getLineNo(), 276 MSG_INDENTATION_ERROR, currentNode.getText(), 277 currentNode.getColumnNo(), currentIndent); 278 } 279 } 280 } 281}