001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 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;
021
022import java.util.Arrays;
023
024import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
025import com.puppycrawl.tools.checkstyle.PropertyType;
026import com.puppycrawl.tools.checkstyle.XdocsPropertyType;
027import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
028import com.puppycrawl.tools.checkstyle.api.DetailAST;
029import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
030import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
031
032/**
033 * <div>
034 * Checks for restricted tokens beneath other tokens.
035 * </div>
036 *
037 * <p>
038 * WARNING: This is a very powerful and flexible check, but, at the same time,
039 * it is low-level and very implementation-dependent because its results depend
040 * on the grammar we use to build abstract syntax trees. Thus, we recommend using
041 * other checks when they provide the desired functionality. Essentially, this
042 * check just works on the level of an abstract syntax tree and knows nothing
043 * about language structures.
044 * </p>
045 * <ul>
046 * <li>
047 * Property {@code limitedTokens} - Specify set of tokens with limited occurrences as descendants.
048 * Type is {@code java.lang.String[]}.
049 * Validation type is {@code tokenTypesSet}.
050 * Default value is {@code ""}.
051 * </li>
052 * <li>
053 * Property {@code maximumDepth} - Specify the maximum depth for descendant counts.
054 * Type is {@code int}.
055 * Default value is {@code 2147483647}.
056 * </li>
057 * <li>
058 * Property {@code maximumMessage} - Define the violation message
059 * when the maximum count is exceeded.
060 * Type is {@code java.lang.String}.
061 * Default value is {@code null}.
062 * </li>
063 * <li>
064 * Property {@code maximumNumber} - Specify a maximum count for descendants.
065 * Type is {@code int}.
066 * Default value is {@code 2147483647}.
067 * </li>
068 * <li>
069 * Property {@code minimumDepth} - Specify the minimum depth for descendant counts.
070 * Type is {@code int}.
071 * Default value is {@code 0}.
072 * </li>
073 * <li>
074 * Property {@code minimumMessage} - Define the violation message
075 * when the minimum count is not reached.
076 * Type is {@code java.lang.String}.
077 * Default value is {@code null}.
078 * </li>
079 * <li>
080 * Property {@code minimumNumber} - Specify a minimum count for descendants.
081 * Type is {@code int}.
082 * Default value is {@code 0}.
083 * </li>
084 * <li>
085 * Property {@code sumTokenCounts} - Control whether the number of tokens found
086 * should be calculated from the sum of the individual token counts.
087 * Type is {@code boolean}.
088 * Default value is {@code false}.
089 * </li>
090 * <li>
091 * Property {@code tokens} - tokens to check
092 * Type is {@code anyTokenTypesSet}.
093 * Default value is {@code ""}.
094 * </li>
095 * </ul>
096 *
097 * <p>
098 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
099 * </p>
100 *
101 * <p>
102 * Violation Message Keys:
103 * </p>
104 * <ul>
105 * <li>
106 * {@code descendant.token.max}
107 * </li>
108 * <li>
109 * {@code descendant.token.min}
110 * </li>
111 * <li>
112 * {@code descendant.token.sum.max}
113 * </li>
114 * <li>
115 * {@code descendant.token.sum.min}
116 * </li>
117 * </ul>
118 *
119 * @since 3.2
120 */
121@FileStatefulCheck
122public class DescendantTokenCheck extends AbstractCheck {
123
124    /**
125     * A key is pointing to the warning message text in "messages.properties"
126     * file.
127     */
128    public static final String MSG_KEY_MIN = "descendant.token.min";
129
130    /**
131     * A key is pointing to the warning message text in "messages.properties"
132     * file.
133     */
134    public static final String MSG_KEY_MAX = "descendant.token.max";
135
136    /**
137     * A key is pointing to the warning message text in "messages.properties"
138     * file.
139     */
140    public static final String MSG_KEY_SUM_MIN = "descendant.token.sum.min";
141
142    /**
143     * A key is pointing to the warning message text in "messages.properties"
144     * file.
145     */
146    public static final String MSG_KEY_SUM_MAX = "descendant.token.sum.max";
147
148    /** Specify the minimum depth for descendant counts. */
149    private int minimumDepth;
150    /** Specify the maximum depth for descendant counts. */
151    private int maximumDepth = Integer.MAX_VALUE;
152    /** Specify a minimum count for descendants. */
153    private int minimumNumber;
154    /** Specify a maximum count for descendants. */
155    private int maximumNumber = Integer.MAX_VALUE;
156    /**
157     * Control whether the number of tokens found should be calculated from
158     * the sum of the individual token counts.
159     */
160    private boolean sumTokenCounts;
161    /** Specify set of tokens with limited occurrences as descendants. */
162    @XdocsPropertyType(PropertyType.TOKEN_ARRAY)
163    private int[] limitedTokens = CommonUtil.EMPTY_INT_ARRAY;
164    /** Define the violation message when the minimum count is not reached. */
165    private String minimumMessage;
166    /** Define the violation message when the maximum count is exceeded. */
167    private String maximumMessage;
168
169    /**
170     * Counts of descendant tokens.
171     * Indexed by (token ID - 1) for performance.
172     */
173    private int[] counts = CommonUtil.EMPTY_INT_ARRAY;
174
175    @Override
176    public int[] getAcceptableTokens() {
177        return TokenUtil.getAllTokenIds();
178    }
179
180    @Override
181    public int[] getDefaultTokens() {
182        return getRequiredTokens();
183    }
184
185    @Override
186    public int[] getRequiredTokens() {
187        return CommonUtil.EMPTY_INT_ARRAY;
188    }
189
190    @Override
191    public void visitToken(DetailAST ast) {
192        // reset counts
193        Arrays.fill(counts, 0);
194        countTokens(ast, 0);
195
196        if (sumTokenCounts) {
197            logAsTotal(ast);
198        }
199        else {
200            logAsSeparated(ast);
201        }
202    }
203
204    /**
205     * Log violations for each Token.
206     *
207     * @param ast token
208     */
209    private void logAsSeparated(DetailAST ast) {
210        // name of this token
211        final String name = TokenUtil.getTokenName(ast.getType());
212
213        for (int element : limitedTokens) {
214            final int tokenCount = counts[element - 1];
215            if (tokenCount < minimumNumber) {
216                final String descendantName = TokenUtil.getTokenName(element);
217
218                if (minimumMessage == null) {
219                    minimumMessage = MSG_KEY_MIN;
220                }
221                log(ast,
222                        minimumMessage,
223                        String.valueOf(tokenCount),
224                        String.valueOf(minimumNumber),
225                        name,
226                        descendantName);
227            }
228            if (tokenCount > maximumNumber) {
229                final String descendantName = TokenUtil.getTokenName(element);
230
231                if (maximumMessage == null) {
232                    maximumMessage = MSG_KEY_MAX;
233                }
234                log(ast,
235                        maximumMessage,
236                        String.valueOf(tokenCount),
237                        String.valueOf(maximumNumber),
238                        name,
239                        descendantName);
240            }
241        }
242    }
243
244    /**
245     * Log validation as one violation.
246     *
247     * @param ast current token
248     */
249    private void logAsTotal(DetailAST ast) {
250        // name of this token
251        final String name = TokenUtil.getTokenName(ast.getType());
252
253        int total = 0;
254        for (int element : limitedTokens) {
255            total += counts[element - 1];
256        }
257        if (total < minimumNumber) {
258            if (minimumMessage == null) {
259                minimumMessage = MSG_KEY_SUM_MIN;
260            }
261            log(ast,
262                    minimumMessage,
263                    String.valueOf(total),
264                    String.valueOf(minimumNumber), name);
265        }
266        if (total > maximumNumber) {
267            if (maximumMessage == null) {
268                maximumMessage = MSG_KEY_SUM_MAX;
269            }
270            log(ast,
271                    maximumMessage,
272                    String.valueOf(total),
273                    String.valueOf(maximumNumber), name);
274        }
275    }
276
277    /**
278     * Counts the number of occurrences of descendant tokens.
279     *
280     * @param ast the root token for descendants.
281     * @param depth the maximum depth of the counted descendants.
282     */
283    private void countTokens(DetailAST ast, int depth) {
284        if (depth <= maximumDepth) {
285            // update count
286            if (depth >= minimumDepth) {
287                final int type = ast.getType();
288                if (type <= counts.length) {
289                    counts[type - 1]++;
290                }
291            }
292            DetailAST child = ast.getFirstChild();
293            final int nextDepth = depth + 1;
294            while (child != null) {
295                countTokens(child, nextDepth);
296                child = child.getNextSibling();
297            }
298        }
299    }
300
301    /**
302     * Setter to specify set of tokens with limited occurrences as descendants.
303     *
304     * @param limitedTokensParam tokens to ignore.
305     * @since 3.2
306     */
307    public void setLimitedTokens(String... limitedTokensParam) {
308        limitedTokens = new int[limitedTokensParam.length];
309
310        int maxToken = 0;
311        for (int i = 0; i < limitedTokensParam.length; i++) {
312            limitedTokens[i] = TokenUtil.getTokenId(limitedTokensParam[i]);
313            if (limitedTokens[i] >= maxToken + 1) {
314                maxToken = limitedTokens[i];
315            }
316        }
317        counts = new int[maxToken];
318    }
319
320    /**
321     * Setter to specify the minimum depth for descendant counts.
322     *
323     * @param minimumDepth the minimum depth for descendant counts.
324     * @since 3.2
325     */
326    public void setMinimumDepth(int minimumDepth) {
327        this.minimumDepth = minimumDepth;
328    }
329
330    /**
331     * Setter to specify the maximum depth for descendant counts.
332     *
333     * @param maximumDepth the maximum depth for descendant counts.
334     * @since 3.2
335     */
336    public void setMaximumDepth(int maximumDepth) {
337        this.maximumDepth = maximumDepth;
338    }
339
340    /**
341     * Setter to specify a minimum count for descendants.
342     *
343     * @param minimumNumber the minimum count for descendants.
344     * @since 3.2
345     */
346    public void setMinimumNumber(int minimumNumber) {
347        this.minimumNumber = minimumNumber;
348    }
349
350    /**
351     * Setter to specify a maximum count for descendants.
352     *
353     * @param maximumNumber the maximum count for descendants.
354     * @since 3.2
355     */
356    public void setMaximumNumber(int maximumNumber) {
357        this.maximumNumber = maximumNumber;
358    }
359
360    /**
361     * Setter to define the violation message when the minimum count is not reached.
362     *
363     * @param message the violation message for minimum count not reached.
364     *     Used as a {@code MessageFormat} pattern with arguments
365     *     <ul>
366     *     <li>{0} - token count</li>
367     *     <li>{1} - minimum number</li>
368     *     <li>{2} - name of token</li>
369     *     <li>{3} - name of limited token</li>
370     *     </ul>
371     * @since 3.2
372     */
373    public void setMinimumMessage(String message) {
374        minimumMessage = message;
375    }
376
377    /**
378     * Setter to define the violation message when the maximum count is exceeded.
379     *
380     * @param message the violation message for maximum count exceeded.
381     *     Used as a {@code MessageFormat} pattern with arguments
382     *     <ul>
383     *     <li>{0} - token count</li>
384     *     <li>{1} - maximum number</li>
385     *     <li>{2} - name of token</li>
386     *     <li>{3} - name of limited token</li>
387     *     </ul>
388     * @since 3.2
389     */
390
391    public void setMaximumMessage(String message) {
392        maximumMessage = message;
393    }
394
395    /**
396     * Setter to control whether the number of tokens found should be calculated
397     * from the sum of the individual token counts.
398     *
399     * @param sum whether to use the sum.
400     * @since 5.0
401     */
402    public void setSumTokenCounts(boolean sum) {
403        sumTokenCounts = sum;
404    }
405
406}