001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2022 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.sizes;
021
022import com.puppycrawl.tools.checkstyle.StatelessCheck;
023import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
024import com.puppycrawl.tools.checkstyle.api.DetailAST;
025import com.puppycrawl.tools.checkstyle.api.TokenTypes;
026
027/**
028 * <p>
029 * Checks lambda body length.
030 * </p>
031 * <p>
032 * Rationale: Similar to anonymous inner classes, if lambda body becomes very long
033 * it is hard to understand and to see the flow of the method
034 * where the lambda is defined. Therefore long lambda body
035 * should usually be extracted to method.
036 * </p>
037 * <ul>
038 * <li>
039 * Property {@code max} - Specify the maximum number of lines allowed.
040 * Type is {@code int}.
041 * Default value is {@code 10}.
042 * </li>
043 * </ul>
044 * <p>
045 * To configure the check to accept lambda bodies with up to 10 lines:
046 * </p>
047 * <pre>
048 * &lt;module name="LambdaBodyLength"/&gt;
049 * </pre>
050 * <p>
051 * Example:
052 * </p>
053 * <pre>
054 * class Test {
055 *   Runnable r = () -&gt; { // violation, 11 lines
056 *       System.out.println(2); // line 2 of lambda
057 *       System.out.println(3);
058 *       System.out.println(4);
059 *       System.out.println(5);
060 *       System.out.println(6);
061 *       System.out.println(7);
062 *       System.out.println(8);
063 *       System.out.println(9);
064 *       System.out.println(10);
065 *   }; // line 11
066 *
067 *   Runnable r2 = () -&gt; // violation, 11 lines
068 *       "someString".concat("1") // line 1 of lambda
069 *                   .concat("2")
070 *                   .concat("3")
071 *                   .concat("4")
072 *                   .concat("5")
073 *                   .concat("6")
074 *                   .concat("7")
075 *                   .concat("8")
076 *                   .concat("9")
077 *                   .concat("10")
078 *                   .concat("11"); // line 11
079 *
080 *   Runnable r3 = () -&gt; { // ok, 10 lines
081 *       System.out.println(2); // line 2 of lambda
082 *       System.out.println(3);
083 *       System.out.println(4);
084 *       System.out.println(5);
085 *       System.out.println(6);
086 *       System.out.println(7);
087 *       System.out.println(8);
088 *       System.out.println(9);
089 *   }; // line 10
090 * }
091 * </pre>
092 * <p>
093 * To configure the check to accept lambda bodies with max 5 lines:
094 * </p>
095 * <pre>
096 * &lt;module name="LambdaBodyLength"&gt;
097 *   &lt;property name="max" value="5"/&gt;
098 * &lt;/module&gt;
099 * </pre>
100 * <p>
101 * Example:
102 * </p>
103 * <pre>
104 * class Test {
105 *   Runnable r = () -&gt; { // violation, 6 lines
106 *       System.out.println(2); // line 2 of lambda
107 *       System.out.println(3);
108 *       System.out.println(4);
109 *       System.out.println(5);
110 *   };
111 *
112 *   Runnable r2 = () -&gt; // violation, 6 lines
113 *       "someString".concat("1")
114 *                   .concat("2")
115 *                   .concat("3")
116 *                   .concat("4")
117 *                   .concat("5")
118 *                   .concat("6");
119 *
120 *   Runnable r3 = () -&gt; { // ok, 5 lines
121 *       System.out.println(2);
122 *       System.out.println(3);
123 *       System.out.println(4);
124 *   };
125 * }
126 * </pre>
127 * <p>
128 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
129 * </p>
130 * <p>
131 * Violation Message Keys:
132 * </p>
133 * <ul>
134 * <li>
135 * {@code maxLen.lambdaBody}
136 * </li>
137 * </ul>
138 *
139 * @since 8.37
140 */
141@StatelessCheck
142public class LambdaBodyLengthCheck extends AbstractCheck {
143
144    /**
145     * A key is pointing to the warning message text in "messages.properties"
146     * file.
147     */
148    public static final String MSG_KEY = "maxLen.lambdaBody";
149
150    /** Default maximum number of lines. */
151    private static final int DEFAULT_MAX = 10;
152
153    /** Specify the maximum number of lines allowed. */
154    private int max = DEFAULT_MAX;
155
156    /**
157     * Setter to specify the maximum number of lines allowed.
158     *
159     * @param length the maximum length of lambda body.
160     */
161    public void setMax(int length) {
162        max = length;
163    }
164
165    @Override
166    public int[] getDefaultTokens() {
167        return getRequiredTokens();
168    }
169
170    @Override
171    public int[] getAcceptableTokens() {
172        return getRequiredTokens();
173    }
174
175    @Override
176    public int[] getRequiredTokens() {
177        return new int[] {TokenTypes.LAMBDA};
178    }
179
180    @Override
181    public void visitToken(DetailAST ast) {
182        if (ast.getParent().getType() != TokenTypes.SWITCH_RULE) {
183            final int length = getLength(ast);
184            if (length > max) {
185                log(ast, MSG_KEY, length, max);
186            }
187        }
188    }
189
190    /**
191     * Get length of lambda body.
192     *
193     * @param ast lambda body node.
194     * @return length of lambda body.
195     */
196    private static int getLength(DetailAST ast) {
197        final DetailAST lambdaBody = ast.getLastChild();
198        final int length;
199        if (lambdaBody.getType() == TokenTypes.SLIST) {
200            length = lambdaBody.getLastChild().getLineNo() - lambdaBody.getLineNo();
201        }
202        else {
203            length = getLastNodeLineNumber(lambdaBody) - getFirstNodeLineNumber(lambdaBody);
204        }
205        return length + 1;
206    }
207
208    /**
209     * Get last child node in the tree line number.
210     *
211     * @param lambdaBody lambda body node.
212     * @return last child node in the tree line number.
213     */
214    private static int getLastNodeLineNumber(DetailAST lambdaBody) {
215        DetailAST node = lambdaBody;
216        int result;
217        do {
218            result = node.getLineNo();
219            node = node.getLastChild();
220        } while (node != null);
221        return result;
222    }
223
224    /**
225     * Get first child node in the tree line number.
226     *
227     * @param lambdaBody lambda body node.
228     * @return first child node in the tree line number.
229     */
230    private static int getFirstNodeLineNumber(DetailAST lambdaBody) {
231        DetailAST node = lambdaBody;
232        int result;
233        do {
234            result = node.getLineNo();
235            node = node.getFirstChild();
236        } while (node != null);
237        return result;
238    }
239
240}