001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 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 *
046 * @since 3.2
047 */
048@FileStatefulCheck
049public class DescendantTokenCheck extends AbstractCheck {
050
051    /**
052     * A key is pointing to the warning message text in "messages.properties" file.
053     */
054    public static final String MSG_KEY_MIN = "descendant.token.min";
055
056    /**
057     * A key is pointing to the warning message text in "messages.properties" file.
058     */
059    public static final String MSG_KEY_MAX = "descendant.token.max";
060
061    /**
062     * A key is pointing to the warning message text in "messages.properties" file.
063     */
064    public static final String MSG_KEY_SUM_MIN = "descendant.token.sum.min";
065
066    /**
067     * A key is pointing to the warning message text in "messages.properties" file.
068     */
069    public static final String MSG_KEY_SUM_MAX = "descendant.token.sum.max";
070
071    /** Specify the minimum depth for descendant counts. */
072    private int minimumDepth;
073    /** Specify the maximum depth for descendant counts. */
074    private int maximumDepth = Integer.MAX_VALUE;
075    /** Specify a minimum count for descendants. */
076    private int minimumNumber;
077    /** Specify a maximum count for descendants. */
078    private int maximumNumber = Integer.MAX_VALUE;
079    /**
080     * Control whether the number of tokens found should be calculated from
081     * the sum of the individual token counts.
082     */
083    private boolean sumTokenCounts;
084    /** Specify set of tokens with limited occurrences as descendants. */
085    @XdocsPropertyType(PropertyType.TOKEN_ARRAY)
086    private int[] limitedTokens = CommonUtil.EMPTY_INT_ARRAY;
087    /** Define the violation message when the minimum count is not reached. */
088    private String minimumMessage;
089    /** Define the violation message when the maximum count is exceeded. */
090    private String maximumMessage;
091
092    /**
093     * Counts of descendant tokens.
094     * Indexed by (token ID - 1) for performance.
095     */
096    private int[] counts = CommonUtil.EMPTY_INT_ARRAY;
097
098    /**
099     * Creates a new {@code DescendantTokenCheck} instance.
100     */
101    public DescendantTokenCheck() {
102        // no code by default
103    }
104
105    @Override
106    public int[] getAcceptableTokens() {
107        return TokenUtil.getAllTokenIds();
108    }
109
110    @Override
111    public int[] getDefaultTokens() {
112        return getRequiredTokens();
113    }
114
115    @Override
116    public int[] getRequiredTokens() {
117        return CommonUtil.EMPTY_INT_ARRAY;
118    }
119
120    @Override
121    public void visitToken(DetailAST ast) {
122        // reset counts
123        Arrays.fill(counts, 0);
124        countTokens(ast);
125
126        if (sumTokenCounts) {
127            logAsTotal(ast);
128        }
129        else {
130            logAsSeparated(ast);
131        }
132    }
133
134    /**
135     * Log violations for each Token.
136     *
137     * @param ast token
138     */
139    private void logAsSeparated(DetailAST ast) {
140        // name of this token
141        final String name = TokenUtil.getTokenName(ast.getType());
142
143        for (int element : limitedTokens) {
144            final int tokenCount = counts[element - 1];
145            if (tokenCount < minimumNumber) {
146                final String descendantName = TokenUtil.getTokenName(element);
147
148                if (minimumMessage == null) {
149                    minimumMessage = MSG_KEY_MIN;
150                }
151                log(ast,
152                        minimumMessage,
153                        String.valueOf(tokenCount),
154                        String.valueOf(minimumNumber),
155                        name,
156                        descendantName);
157            }
158            if (tokenCount > maximumNumber) {
159                final String descendantName = TokenUtil.getTokenName(element);
160
161                if (maximumMessage == null) {
162                    maximumMessage = MSG_KEY_MAX;
163                }
164                log(ast,
165                        maximumMessage,
166                        String.valueOf(tokenCount),
167                        String.valueOf(maximumNumber),
168                        name,
169                        descendantName);
170            }
171        }
172    }
173
174    /**
175     * Log validation as one violation.
176     *
177     * @param ast current token
178     */
179    private void logAsTotal(DetailAST ast) {
180        // name of this token
181        final String name = TokenUtil.getTokenName(ast.getType());
182
183        int total = 0;
184        for (int element : limitedTokens) {
185            total += counts[element - 1];
186        }
187        if (total < minimumNumber) {
188            if (minimumMessage == null) {
189                minimumMessage = MSG_KEY_SUM_MIN;
190            }
191            log(ast,
192                    minimumMessage,
193                    String.valueOf(total),
194                    String.valueOf(minimumNumber), name);
195        }
196        if (total > maximumNumber) {
197            if (maximumMessage == null) {
198                maximumMessage = MSG_KEY_SUM_MAX;
199            }
200            log(ast,
201                    maximumMessage,
202                    String.valueOf(total),
203                    String.valueOf(maximumNumber), name);
204        }
205    }
206
207    /**
208     * Counts the number of occurrences of descendant tokens.
209     * The tree is traversed iteratively, as deeply nested trees
210     * (for example long string concatenations) would overflow the stack.
211     *
212     * @param root the root token for descendants.
213     */
214    private void countTokens(DetailAST root) {
215        DetailAST node = null;
216        if (maximumDepth >= 0) {
217            node = root;
218        }
219        int depth = 0;
220        while (node != null) {
221            // update count
222            if (depth >= minimumDepth) {
223                final int type = node.getType();
224                if (type <= counts.length) {
225                    counts[type - 1]++;
226                }
227            }
228            DetailAST next = null;
229            if (depth < maximumDepth) {
230                next = node.getFirstChild();
231            }
232            if (next == null) {
233                while (next == null && depth > 0) {
234                    next = node.getNextSibling();
235                    if (next == null) {
236                        node = node.getParent();
237                        depth--;
238                    }
239                }
240            }
241            else {
242                depth++;
243            }
244            node = next;
245        }
246    }
247
248    /**
249     * Setter to specify set of tokens with limited occurrences as descendants.
250     *
251     * @param limitedTokensParam tokens to ignore.
252     * @since 3.2
253     */
254    public void setLimitedTokens(String... limitedTokensParam) {
255        limitedTokens = new int[limitedTokensParam.length];
256
257        int maxToken = 0;
258        for (int index = 0; index < limitedTokensParam.length; index++) {
259            limitedTokens[index] = TokenUtil.getTokenId(limitedTokensParam[index]);
260            if (limitedTokens[index] >= maxToken + 1) {
261                maxToken = limitedTokens[index];
262            }
263        }
264        counts = new int[maxToken];
265    }
266
267    /**
268     * Setter to specify the minimum depth for descendant counts.
269     *
270     * @param minimumDepth the minimum depth for descendant counts.
271     * @since 3.2
272     */
273    public void setMinimumDepth(int minimumDepth) {
274        this.minimumDepth = minimumDepth;
275    }
276
277    /**
278     * Setter to specify the maximum depth for descendant counts.
279     *
280     * @param maximumDepth the maximum depth for descendant counts.
281     * @since 3.2
282     */
283    public void setMaximumDepth(int maximumDepth) {
284        this.maximumDepth = maximumDepth;
285    }
286
287    /**
288     * Setter to specify a minimum count for descendants.
289     *
290     * @param minimumNumber the minimum count for descendants.
291     * @since 3.2
292     */
293    public void setMinimumNumber(int minimumNumber) {
294        this.minimumNumber = minimumNumber;
295    }
296
297    /**
298     * Setter to specify a maximum count for descendants.
299     *
300     * @param maximumNumber the maximum count for descendants.
301     * @since 3.2
302     */
303    public void setMaximumNumber(int maximumNumber) {
304        this.maximumNumber = maximumNumber;
305    }
306
307    /**
308     * Setter to define the violation message when the minimum count is not reached.
309     *
310     * @param message the violation message for minimum count not reached.
311     *     Used as a {@code MessageFormat} pattern with arguments
312     *     <ul>
313     *     <li>{0} - token count</li>
314     *     <li>{1} - minimum number</li>
315     *     <li>{2} - name of token</li>
316     *     <li>{3} - name of limited token</li>
317     *     </ul>
318     * @since 3.2
319     */
320    public void setMinimumMessage(String message) {
321        minimumMessage = message;
322    }
323
324    /**
325     * Setter to define the violation message when the maximum count is exceeded.
326     *
327     * @param message the violation message for maximum count exceeded.
328     *     Used as a {@code MessageFormat} pattern with arguments
329     *     <ul>
330     *     <li>{0} - token count</li>
331     *     <li>{1} - maximum number</li>
332     *     <li>{2} - name of token</li>
333     *     <li>{3} - name of limited token</li>
334     *     </ul>
335     * @since 3.2
336     */
337
338    public void setMaximumMessage(String message) {
339        maximumMessage = message;
340    }
341
342    /**
343     * Setter to control whether the number of tokens found should be calculated
344     * from the sum of the individual token counts.
345     *
346     * @param sum whether to use the sum.
347     * @since 5.0
348     */
349    public void setSumTokenCounts(boolean sum) {
350        sumTokenCounts = sum;
351    }
352
353}