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