Coverage Report - com.puppycrawl.tools.checkstyle.checks.indentation.LineWrappingHandler
 
Classes in this File Line Coverage Branch Coverage Complexity
LineWrappingHandler
100%
110/110
100%
64/64
3.357
LineWrappingHandler$LineWrappingOptions
100%
7/7
100%
2/2
3.357
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2017 the original author or authors.
 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  
 
 20  
 package com.puppycrawl.tools.checkstyle.checks.indentation;
 21  
 
 22  
 import java.util.Collection;
 23  
 import java.util.Iterator;
 24  
 import java.util.NavigableMap;
 25  
 import java.util.TreeMap;
 26  
 
 27  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 28  
 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
 29  
 import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
 30  
 
 31  
 /**
 32  
  * This class checks line-wrapping into definitions and expressions. The
 33  
  * line-wrapping indentation should be not less then value of the
 34  
  * lineWrappingIndentation parameter.
 35  
  *
 36  
  * @author maxvetrenko
 37  
  * @author <a href="mailto:piotr.listkiewicz@gmail.com">liscju</a>
 38  
  */
 39  
 public class LineWrappingHandler {
 40  
 
 41  
     /**
 42  
      * Enum to be used for test if first line's indentation should be checked or not.
 43  
      */
 44  6
     public enum LineWrappingOptions {
 45  
         /**
 46  
          * First line's indentation should NOT be checked.
 47  
          */
 48  2
         IGNORE_FIRST_LINE,
 49  
         /**
 50  
          * First line's indentation should be checked.
 51  
          */
 52  2
         NONE;
 53  
 
 54  
         /**
 55  
          * Builds enum value from boolean.
 56  
          * @param val value.
 57  
          * @return enum instance.
 58  
          *
 59  
          * @noinspection BooleanParameter
 60  
          */
 61  
         public static LineWrappingOptions ofBoolean(boolean val) {
 62  116
             LineWrappingOptions option = NONE;
 63  116
             if (val) {
 64  52
                 option = IGNORE_FIRST_LINE;
 65  
             }
 66  116
             return option;
 67  
         }
 68  
     }
 69  
 
 70  
     /**
 71  
      * The current instance of {@code IndentationCheck} class using this
 72  
      * handler. This field used to get access to private fields of
 73  
      * IndentationCheck instance.
 74  
      */
 75  
     private final IndentationCheck indentCheck;
 76  
 
 77  
     /**
 78  
      * Sets values of class field, finds last node and calculates indentation level.
 79  
      *
 80  
      * @param instance
 81  
      *            instance of IndentationCheck.
 82  
      */
 83  89
     public LineWrappingHandler(IndentationCheck instance) {
 84  89
         indentCheck = instance;
 85  89
     }
 86  
 
 87  
     /**
 88  
      * Checks line wrapping into expressions and definitions using property
 89  
      * 'lineWrappingIndentation'.
 90  
      *
 91  
      * @param firstNode First node to start examining.
 92  
      * @param lastNode Last node to examine inclusively.
 93  
      */
 94  
     public void checkIndentation(DetailAST firstNode, DetailAST lastNode) {
 95  1200
         checkIndentation(firstNode, lastNode, indentCheck.getLineWrappingIndentation());
 96  1200
     }
 97  
 
 98  
     /**
 99  
      * Checks line wrapping into expressions and definitions.
 100  
      *
 101  
      * @param firstNode First node to start examining.
 102  
      * @param lastNode Last node to examine inclusively.
 103  
      * @param indentLevel Indentation all wrapped lines should use.
 104  
      */
 105  
     private void checkIndentation(DetailAST firstNode, DetailAST lastNode, int indentLevel) {
 106  1200
         checkIndentation(firstNode, lastNode, indentLevel,
 107  
                 -1, LineWrappingOptions.IGNORE_FIRST_LINE);
 108  1200
     }
 109  
 
 110  
     /**
 111  
      * Checks line wrapping into expressions and definitions.
 112  
      *
 113  
      * @param firstNode First node to start examining.
 114  
      * @param lastNode Last node to examine inclusively.
 115  
      * @param indentLevel Indentation all wrapped lines should use.
 116  
      * @param startIndent Indentation first line before wrapped lines used.
 117  
      * @param ignoreFirstLine Test if first line's indentation should be checked or not.
 118  
      */
 119  
     public void checkIndentation(DetailAST firstNode, DetailAST lastNode, int indentLevel,
 120  
             int startIndent, LineWrappingOptions ignoreFirstLine) {
 121  1316
         final NavigableMap<Integer, DetailAST> firstNodesOnLines = collectFirstNodes(firstNode,
 122  
                 lastNode);
 123  
 
 124  1316
         final DetailAST firstLineNode = firstNodesOnLines.get(firstNodesOnLines.firstKey());
 125  1316
         if (firstLineNode.getType() == TokenTypes.AT) {
 126  140
             DetailAST node = firstLineNode.getParent();
 127  620
             while (node != null) {
 128  480
                 if (node.getType() == TokenTypes.ANNOTATION) {
 129  153
                     final DetailAST atNode = node.getFirstChild();
 130  153
                     final NavigableMap<Integer, DetailAST> annotationLines =
 131  153
                         firstNodesOnLines.subMap(
 132  153
                             node.getLineNo(),
 133  
                             true,
 134  153
                             getNextNodeLine(firstNodesOnLines, node),
 135  
                             true
 136  
                         );
 137  153
                     checkAnnotationIndentation(atNode, annotationLines, indentLevel);
 138  
                 }
 139  480
                 node = node.getNextSibling();
 140  
             }
 141  
         }
 142  
 
 143  1316
         if (ignoreFirstLine == LineWrappingOptions.IGNORE_FIRST_LINE) {
 144  
             // First node should be removed because it was already checked before.
 145  1252
             firstNodesOnLines.remove(firstNodesOnLines.firstKey());
 146  
         }
 147  
 
 148  
         final int firstNodeIndent;
 149  1316
         if (startIndent == -1) {
 150  1200
             firstNodeIndent = getLineStart(firstLineNode);
 151  
         }
 152  
         else {
 153  116
             firstNodeIndent = startIndent;
 154  
         }
 155  1316
         final int currentIndent = firstNodeIndent + indentLevel;
 156  
 
 157  1316
         for (DetailAST node : firstNodesOnLines.values()) {
 158  522
             final int currentType = node.getType();
 159  
 
 160  522
             if (currentType == TokenTypes.RPAREN) {
 161  11
                 logWarningMessage(node, firstNodeIndent);
 162  
             }
 163  511
             else if (currentType != TokenTypes.RCURLY && currentType != TokenTypes.ARRAY_INIT) {
 164  457
                 logWarningMessage(node, currentIndent);
 165  
             }
 166  522
         }
 167  1316
     }
 168  
 
 169  
     /**
 170  
      * Gets the next node line from the firstNodesOnLines map unless there is no next line, in
 171  
      * which case, it returns the last line.
 172  
      *
 173  
      * @param firstNodesOnLines NavigableMap of lines and their first nodes.
 174  
      * @param node the node for which to find the next node line
 175  
      * @return the line number of the next line in the map
 176  
      */
 177  
     private static Integer getNextNodeLine(
 178  
             NavigableMap<Integer, DetailAST> firstNodesOnLines, DetailAST node) {
 179  153
         Integer nextNodeLine = firstNodesOnLines.higherKey(node.getLastChild().getLineNo());
 180  153
         if (nextNodeLine == null) {
 181  6
             nextNodeLine = firstNodesOnLines.lastKey();
 182  
         }
 183  153
         return nextNodeLine;
 184  
     }
 185  
 
 186  
     /**
 187  
      * Finds first nodes on line and puts them into Map.
 188  
      *
 189  
      * @param firstNode First node to start examining.
 190  
      * @param lastNode Last node to examine inclusively.
 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(DetailAST firstNode,
 195  
             DetailAST lastNode) {
 196  1316
         final NavigableMap<Integer, DetailAST> result = new TreeMap<>();
 197  
 
 198  1316
         result.put(firstNode.getLineNo(), firstNode);
 199  1316
         DetailAST curNode = firstNode.getFirstChild();
 200  
 
 201  17730
         while (curNode != lastNode) {
 202  
 
 203  16414
             if (curNode.getType() == TokenTypes.OBJBLOCK
 204  16382
                     || curNode.getType() == TokenTypes.SLIST) {
 205  52
                 curNode = curNode.getLastChild();
 206  
             }
 207  
 
 208  16414
             final DetailAST firstTokenOnLine = result.get(curNode.getLineNo());
 209  
 
 210  16414
             if (firstTokenOnLine == null
 211  15731
                 || expandedTabsColumnNo(firstTokenOnLine) >= expandedTabsColumnNo(curNode)) {
 212  3867
                 result.put(curNode.getLineNo(), curNode);
 213  
             }
 214  16414
             curNode = getNextCurNode(curNode);
 215  16414
         }
 216  1316
         return result;
 217  
     }
 218  
 
 219  
     /**
 220  
      * Returns next curNode node.
 221  
      *
 222  
      * @param curNode current node.
 223  
      * @return next curNode node.
 224  
      */
 225  
     private static DetailAST getNextCurNode(DetailAST curNode) {
 226  16414
         DetailAST nodeToVisit = curNode.getFirstChild();
 227  16414
         DetailAST currentNode = curNode;
 228  
 
 229  33183
         while (nodeToVisit == null) {
 230  16769
             nodeToVisit = currentNode.getNextSibling();
 231  16769
             if (nodeToVisit == null) {
 232  6577
                 currentNode = currentNode.getParent();
 233  
             }
 234  
         }
 235  16414
         return nodeToVisit;
 236  
     }
 237  
 
 238  
     /**
 239  
      * Checks line wrapping into annotations.
 240  
      *
 241  
      * @param atNode at-clause node.
 242  
      * @param firstNodesOnLines map which contains
 243  
      *     first nodes as values and line numbers as keys.
 244  
      * @param indentLevel line wrapping indentation.
 245  
      */
 246  
     private void checkAnnotationIndentation(DetailAST atNode,
 247  
             NavigableMap<Integer, DetailAST> firstNodesOnLines, int indentLevel) {
 248  153
         final int firstNodeIndent = getLineStart(atNode);
 249  153
         final int currentIndent = firstNodeIndent + indentLevel;
 250  153
         final Collection<DetailAST> values = firstNodesOnLines.values();
 251  153
         final DetailAST lastAnnotationNode = atNode.getParent().getLastChild();
 252  153
         final int lastAnnotationLine = lastAnnotationNode.getLineNo();
 253  
 
 254  153
         final Iterator<DetailAST> itr = values.iterator();
 255  378
         while (firstNodesOnLines.size() > 1) {
 256  225
             final DetailAST node = itr.next();
 257  
 
 258  225
             final DetailAST parentNode = node.getParent();
 259  225
             final boolean isCurrentNodeCloseAnnotationAloneInLine =
 260  225
                 node.getLineNo() == lastAnnotationLine
 261  144
                     && isEndOfScope(lastAnnotationNode, node);
 262  225
             if (isCurrentNodeCloseAnnotationAloneInLine
 263  189
                     || node.getType() == TokenTypes.AT
 264  163
                     && (parentNode.getParent().getType() == TokenTypes.MODIFIERS
 265  20
                         || parentNode.getParent().getType() == TokenTypes.ANNOTATIONS)) {
 266  180
                 logWarningMessage(node, firstNodeIndent);
 267  
             }
 268  
             else {
 269  45
                 logWarningMessage(node, currentIndent);
 270  
             }
 271  225
             itr.remove();
 272  225
         }
 273  153
     }
 274  
 
 275  
     /**
 276  
      * Checks line for end of scope.  Handles occurrences of close braces and close parenthesis on
 277  
      * the same line.
 278  
      *
 279  
      * @param lastAnnotationNode the last node of the annotation
 280  
      * @param node the node indicating where to begin checking
 281  
      * @return true if all the nodes up to the last annotation node are end of scope nodes
 282  
      *         false otherwise
 283  
      */
 284  
     private static boolean isEndOfScope(final DetailAST lastAnnotationNode, final DetailAST node) {
 285  144
         DetailAST checkNode = node;
 286  144
         boolean endOfScope = true;
 287  256
         while (endOfScope && !checkNode.equals(lastAnnotationNode)) {
 288  112
             switch (checkNode.getType()) {
 289  
                 case TokenTypes.RCURLY:
 290  
                 case TokenTypes.RBRACK:
 291  9
                     while (checkNode.getNextSibling() == null) {
 292  5
                         checkNode = checkNode.getParent();
 293  
                     }
 294  4
                     checkNode = checkNode.getNextSibling();
 295  4
                     break;
 296  
                 default:
 297  108
                     endOfScope = false;
 298  
 
 299  
             }
 300  
 
 301  
         }
 302  144
         return endOfScope;
 303  
     }
 304  
 
 305  
     /**
 306  
      * Get the column number for the start of a given expression, expanding
 307  
      * tabs out into spaces in the process.
 308  
      *
 309  
      * @param ast   the expression to find the start of
 310  
      *
 311  
      * @return the column number for the start of the expression
 312  
      */
 313  
     private int expandedTabsColumnNo(DetailAST ast) {
 314  32253
         final String line =
 315  32253
             indentCheck.getLine(ast.getLineNo() - 1);
 316  
 
 317  64506
         return CommonUtils.lengthExpandedTabs(line, ast.getColumnNo(),
 318  32253
             indentCheck.getIndentationTabWidth());
 319  
     }
 320  
 
 321  
     /**
 322  
      * Get the start of the line for the given expression.
 323  
      *
 324  
      * @param ast   the expression to find the start of the line for
 325  
      *
 326  
      * @return the start of the line for the given expression
 327  
      */
 328  
     private int getLineStart(DetailAST ast) {
 329  1353
         final String line = indentCheck.getLine(ast.getLineNo() - 1);
 330  1353
         return getLineStart(line);
 331  
     }
 332  
 
 333  
     /**
 334  
      * Get the start of the specified line.
 335  
      *
 336  
      * @param line the specified line number
 337  
      * @return the start of the specified line
 338  
      */
 339  
     private int getLineStart(String line) {
 340  1353
         int index = 0;
 341  7229
         while (Character.isWhitespace(line.charAt(index))) {
 342  5876
             index++;
 343  
         }
 344  1353
         return CommonUtils.lengthExpandedTabs(line, index, indentCheck.getIndentationTabWidth());
 345  
     }
 346  
 
 347  
     /**
 348  
      * Logs warning message if indentation is incorrect.
 349  
      *
 350  
      * @param currentNode
 351  
      *            current node which probably invoked an error.
 352  
      * @param currentIndent
 353  
      *            correct indentation.
 354  
      */
 355  
     private void logWarningMessage(DetailAST currentNode, int currentIndent) {
 356  693
         if (indentCheck.isForceStrictCondition()) {
 357  243
             if (expandedTabsColumnNo(currentNode) != currentIndent) {
 358  82
                 indentCheck.indentationLog(currentNode.getLineNo(),
 359  41
                         IndentationCheck.MSG_ERROR, currentNode.getText(),
 360  41
                         expandedTabsColumnNo(currentNode), currentIndent);
 361  
             }
 362  
         }
 363  
         else {
 364  450
             if (expandedTabsColumnNo(currentNode) < currentIndent) {
 365  114
                 indentCheck.indentationLog(currentNode.getLineNo(),
 366  57
                         IndentationCheck.MSG_ERROR, currentNode.getText(),
 367  57
                         expandedTabsColumnNo(currentNode), currentIndent);
 368  
             }
 369  
         }
 370  693
     }
 371  
 }