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