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.utils;
021
022import java.util.Optional;
023
024import com.puppycrawl.tools.checkstyle.api.DetailAST;
025import com.puppycrawl.tools.checkstyle.api.Scope;
026import com.puppycrawl.tools.checkstyle.api.TokenTypes;
027
028/**
029 * Contains utility methods for working on scope.
030 *
031 */
032public final class ScopeUtil {
033
034    /** Prevent instantiation. */
035    private ScopeUtil() {
036    }
037
038    /**
039     * Returns the {@code Scope} explicitly specified by the modifier set.
040     * Returns {@code null} if there are no modifiers.
041     *
042     * @param aMods root node of a modifier set
043     * @return a {@code Scope} value or {@code null}
044     */
045    public static Scope getDeclaredScopeFromMods(DetailAST aMods) {
046        Scope result = null;
047        for (DetailAST token = aMods.getFirstChild(); token != null;
048             token = token.getNextSibling()) {
049            switch (token.getType()) {
050                case TokenTypes.LITERAL_PUBLIC:
051                    result = Scope.PUBLIC;
052                    break;
053                case TokenTypes.LITERAL_PROTECTED:
054                    result = Scope.PROTECTED;
055                    break;
056                case TokenTypes.LITERAL_PRIVATE:
057                    result = Scope.PRIVATE;
058                    break;
059                default:
060                    break;
061            }
062        }
063        return result;
064    }
065
066    /**
067     * Returns the {@code Scope} for a given {@code DetailAST}.
068     *
069     * @param ast the DetailAST to examine
070     * @return a {@code Scope} value
071     */
072    public static Scope getScope(DetailAST ast) {
073        return Optional.ofNullable(ast.findFirstToken(TokenTypes.MODIFIERS))
074                .map(ScopeUtil::getDeclaredScopeFromMods)
075                .orElseGet(() -> getDefaultScope(ast));
076    }
077
078    /**
079     * Returns the {@code Scope} specified by the modifier set. If no modifiers are present,
080     * the default scope is used.
081     *
082     * @param aMods root node of a modifier set
083     * @return a {@code Scope} value
084     * @see #getDefaultScope(DetailAST)
085     */
086    public static Scope getScopeFromMods(DetailAST aMods) {
087        return Optional.ofNullable(getDeclaredScopeFromMods(aMods))
088                .orElseGet(() -> getDefaultScope(aMods));
089    }
090
091    /**
092     * Returns the default {@code Scope} for a {@code DetailAST}.
093     *
094     * <p>The following rules are taken into account:</p>
095     * <ul>
096     *     <li>enum constants are public</li>
097     *     <li>enum constructors are private</li>
098     *     <li>interface members are public</li>
099     *     <li>everything else is package private</li>
100     * </ul>
101     *
102     * @param ast DetailAST to process
103     * @return a {@code Scope} value
104     */
105    private static Scope getDefaultScope(DetailAST ast) {
106        final Scope result;
107        if (isInEnumBlock(ast)) {
108            if (ast.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
109                result = Scope.PUBLIC;
110            }
111            else if (ast.getType() == TokenTypes.CTOR_DEF) {
112                result = Scope.PRIVATE;
113            }
114            else {
115                result = Scope.PACKAGE;
116            }
117        }
118        else if (isInInterfaceOrAnnotationBlock(ast)) {
119            result = Scope.PUBLIC;
120        }
121        else {
122            result = Scope.PACKAGE;
123        }
124        return result;
125    }
126
127    /**
128     * Returns the scope of the surrounding "block".
129     *
130     * @param node the node to return the scope for
131     * @return the Scope of the surrounding block
132     */
133    public static Scope getSurroundingScope(DetailAST node) {
134        Scope returnValue = null;
135        for (DetailAST token = node;
136             token != null;
137             token = token.getParent()) {
138            final int type = token.getType();
139            if (TokenUtil.isTypeDeclaration(type)) {
140                final Scope tokenScope = getScope(token);
141                if (returnValue == null || returnValue.isIn(tokenScope)) {
142                    returnValue = tokenScope;
143                }
144            }
145            else if (type == TokenTypes.LITERAL_NEW) {
146                returnValue = Scope.ANONINNER;
147                // because Scope.ANONINNER is not in any other Scope
148                break;
149            }
150        }
151
152        return returnValue;
153    }
154
155    /**
156     * Returns whether a node is directly contained within a class block.
157     *
158     * @param node the node to check if directly contained within a class block.
159     * @return a {@code boolean} value
160     */
161    public static boolean isInClassBlock(DetailAST node) {
162        return isInBlockOf(node, TokenTypes.CLASS_DEF);
163    }
164
165    /**
166     * Returns whether a node is directly contained within a record block.
167     *
168     * @param node the node to check if directly contained within a record block.
169     * @return a {@code boolean} value
170     */
171    public static boolean isInRecordBlock(DetailAST node) {
172        return isInBlockOf(node, TokenTypes.RECORD_DEF);
173    }
174
175    /**
176     * Returns whether a node is directly contained within an interface block.
177     *
178     * @param node the node to check if directly contained within an interface block.
179     * @return a {@code boolean} value
180     */
181    public static boolean isInInterfaceBlock(DetailAST node) {
182        return isInBlockOf(node, TokenTypes.INTERFACE_DEF);
183    }
184
185    /**
186     * Returns whether a node is directly contained within an annotation block.
187     *
188     * @param node the node to check if directly contained within an annotation block.
189     * @return a {@code boolean} value
190     */
191    public static boolean isInAnnotationBlock(DetailAST node) {
192        return isInBlockOf(node, TokenTypes.ANNOTATION_DEF);
193    }
194
195    /**
196     * Returns whether a node is directly contained within a specified block.
197     *
198     * @param node the node to check if directly contained within a specified block.
199     * @param tokenType type of token.
200     * @return a {@code boolean} value
201     */
202    private static boolean isInBlockOf(DetailAST node, int tokenType) {
203        boolean returnValue = false;
204
205        // Loop up looking for a containing interface block
206        for (DetailAST token = node.getParent();
207             token != null && !returnValue;
208             token = token.getParent()) {
209            if (token.getType() == tokenType) {
210                returnValue = true;
211            }
212            else if (token.getType() == TokenTypes.LITERAL_NEW
213                    || TokenUtil.isTypeDeclaration(token.getType())) {
214                break;
215            }
216        }
217
218        return returnValue;
219    }
220
221    /**
222     * Returns whether a node is directly contained within an interface or
223     * annotation block.
224     *
225     * @param node the node to check if directly contained within an interface
226     *     or annotation block.
227     * @return a {@code boolean} value
228     */
229    public static boolean isInInterfaceOrAnnotationBlock(DetailAST node) {
230        return isInInterfaceBlock(node) || isInAnnotationBlock(node);
231    }
232
233    /**
234     * Returns whether a node is directly contained within an enum block.
235     *
236     * @param node the node to check if directly contained within an enum block.
237     * @return a {@code boolean} value
238     */
239    public static boolean isInEnumBlock(DetailAST node) {
240        boolean returnValue = false;
241
242        // Loop up looking for a containing interface block
243        for (DetailAST token = node.getParent();
244             token != null; token = token.getParent()) {
245            if (TokenUtil.isOfType(token, TokenTypes.INTERFACE_DEF,
246                TokenTypes.ANNOTATION_DEF, TokenTypes.CLASS_DEF,
247                TokenTypes.LITERAL_NEW, TokenTypes.ENUM_DEF)) {
248                returnValue = token.getType() == TokenTypes.ENUM_DEF;
249                break;
250            }
251        }
252
253        return returnValue;
254    }
255
256    /**
257     * Returns whether the scope of a node is restricted to a code block.
258     * A code block is a method or constructor body, an initializer block, or lambda body.
259     *
260     * @param node the node to check
261     * @return a {@code boolean} value
262     */
263    public static boolean isInCodeBlock(DetailAST node) {
264        boolean returnValue = false;
265        final int[] tokenTypes = {
266            TokenTypes.METHOD_DEF,
267            TokenTypes.CTOR_DEF,
268            TokenTypes.INSTANCE_INIT,
269            TokenTypes.STATIC_INIT,
270            TokenTypes.LAMBDA,
271            TokenTypes.COMPACT_CTOR_DEF,
272        };
273
274        // Loop up looking for a containing code block
275        for (DetailAST token = node.getParent();
276             token != null;
277             token = token.getParent()) {
278            if (TokenUtil.isOfType(token, tokenTypes)) {
279                returnValue = true;
280                break;
281            }
282        }
283
284        return returnValue;
285    }
286
287    /**
288     * Returns whether a node is contained in the outermost type block.
289     *
290     * @param node the node to check
291     * @return a {@code boolean} value
292     */
293    public static boolean isOuterMostType(DetailAST node) {
294        boolean returnValue = true;
295        for (DetailAST parent = node.getParent();
296             parent != null;
297             parent = parent.getParent()) {
298            if (TokenUtil.isTypeDeclaration(parent.getType())) {
299                returnValue = false;
300                break;
301            }
302        }
303
304        return returnValue;
305    }
306
307    /**
308     * Determines whether a node is a local variable definition.
309     * I.e. if it is declared in a code block, a for initializer,
310     * or a catch parameter.
311     *
312     * @param node the node to check.
313     * @return whether aAST is a local variable definition.
314     */
315    public static boolean isLocalVariableDef(DetailAST node) {
316        final boolean localVariableDef;
317        // variable declaration?
318        if (node.getType() == TokenTypes.VARIABLE_DEF) {
319            final DetailAST parent = node.getParent();
320            localVariableDef = TokenUtil.isOfType(parent, TokenTypes.SLIST,
321                                TokenTypes.FOR_INIT, TokenTypes.FOR_EACH_CLAUSE);
322        }
323
324        else if (node.getType() == TokenTypes.RESOURCE) {
325            localVariableDef = node.getChildCount() > 1;
326        }
327
328        // catch parameter?
329        else if (node.getType() == TokenTypes.PARAMETER_DEF) {
330            final DetailAST parent = node.getParent();
331            localVariableDef = parent.getType() == TokenTypes.LITERAL_CATCH;
332        }
333
334        else {
335            localVariableDef = false;
336        }
337
338        return localVariableDef;
339    }
340
341    /**
342     * Determines whether a node is a class field definition.
343     * I.e. if a variable is not declared in a code block, a for initializer,
344     * or a catch parameter.
345     *
346     * @param node the node to check.
347     * @return whether a node is a class field definition.
348     */
349    public static boolean isClassFieldDef(DetailAST node) {
350        return node.getType() == TokenTypes.VARIABLE_DEF
351                && !isLocalVariableDef(node);
352    }
353
354    /**
355     * Checks whether ast node is in a specific scope.
356     *
357     * @param ast the node to check.
358     * @param scope a {@code Scope} value.
359     * @return true if the ast node is in the scope.
360     */
361    public static boolean isInScope(DetailAST ast, Scope scope) {
362        final Scope surroundingScopeOfAstToken = getSurroundingScope(ast);
363        return surroundingScopeOfAstToken == scope;
364    }
365
366}