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